ИУП: интенсив 7 дней + диагностический пакет (32 вопроса, 9 задач с автопроверкой)
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:01 +0700] "GET /index.html HTTP/1.1" 200 512
|
||||
10.0.0.2 - - [23/Sep/2026:10:00:02 +0700] "GET /a HTTP/1.1" 404 128
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:03 +0700] "POST /api HTTP/1.1" 500 42
|
||||
10.0.0.3 - - [23/Sep/2026:10:00:04 +0700] "GET /b HTTP/1.1" 200 1024
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:05 +0700] "GET /c HTTP/1.1" 503 0
|
||||
10.0.0.2 - - [23/Sep/2026:10:00:06 +0700] "GET /d HTTP/1.1" 200 33
|
||||
10.0.0.4 - - [23/Sep/2026:10:00:07 +0700] "GET /e HTTP/1.1" 200 10
|
||||
10.0.0.3 - - [23/Sep/2026:10:00:08 +0700] "GET /f HTTP/1.1" 200 11
|
||||
10.0.0.2 - - [23/Sep/2026:10:00:09 +0700] "GET /g HTTP/1.1" 200 12
|
||||
10.0.0.5 - - [23/Sep/2026:10:00:10 +0700] "GET /h HTTP/1.1" 500 13
|
||||
10.0.0.1 - - [23/Sep/2026:10:00:11 +0700] "GET /i HTTP/1.1" 200 14
|
||||
10.0.0.6 - - [23/Sep/2026:10:00:12 +0700] "GET /j HTTP/1.1" 200 15
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Задача 08 — разбор лога доступа. Реализуй.
|
||||
set -euo pipefail
|
||||
|
||||
log="${1:-}"
|
||||
if [[ -z "$log" ]]; then
|
||||
echo "usage: $0 <access.log>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "TOTAL 0"
|
||||
# TODO
|
||||
exit 0
|
||||
@@ -0,0 +1,25 @@
|
||||
# Задача 08 — bash: разбор лога доступа
|
||||
|
||||
Написать `solution.sh`, который принимает путь к файлу лога (формат nginx/apache access.log)
|
||||
и печатает ровно четыре строки:
|
||||
|
||||
```
|
||||
TOTAL <число строк в файле>
|
||||
TOP <ip> <число запросов>
|
||||
TOP <ip> <число запросов>
|
||||
TOP <ip> <число запросов>
|
||||
5XX <число ответов со статусом 500-599>
|
||||
```
|
||||
|
||||
Правила:
|
||||
- IP — первое поле строки;
|
||||
- TOP — три самых частых IP по убыванию числа запросов; при равенстве — по возрастанию
|
||||
строки IP (лексикографически);
|
||||
- если уникальных IP меньше трёх — печатать столько строк TOP, сколько есть;
|
||||
- файл может быть большим (миллион строк): построчный bash-цикл по строкам не пройдёт
|
||||
по времени — нужен awk/sort или один-два прохода;
|
||||
- пустой файл: `TOTAL 0`, `5XX 0`, строк TOP нет.
|
||||
|
||||
Запуск: `bash solution.sh access.log`
|
||||
Проверка: `python3 grade.py 08` (тест гоняет скрипт на фикстуре и на большом логе).
|
||||
Критерий: точное совпадение вывода, код возврата 0.
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Тест задачи 08: сравнение вывода solution.sh с эталоном на фикстуре и большом логе."""
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SCRIPT = os.path.join(HERE, "solution.sh")
|
||||
failures = 0
|
||||
|
||||
|
||||
def check(cond, name, extra=""):
|
||||
global failures
|
||||
if cond:
|
||||
print(f"ok {name}")
|
||||
else:
|
||||
print(f"FAIL {name} {extra}")
|
||||
failures += 1
|
||||
|
||||
|
||||
def run(path):
|
||||
r = subprocess.run(["bash", SCRIPT, path], capture_output=True, text=True, timeout=120)
|
||||
return r.returncode, r.stdout, r.stderr
|
||||
|
||||
|
||||
def expected(lines):
|
||||
from collections import Counter
|
||||
ips = [l.split()[0] for l in lines if l.strip()]
|
||||
total = len(lines)
|
||||
cnt = Counter(ips)
|
||||
top = sorted(cnt.items(), key=lambda kv: (-kv[1], kv[0]))[:3]
|
||||
five = 0
|
||||
for l in lines:
|
||||
parts = l.split('"')
|
||||
if len(parts) >= 3:
|
||||
codes = parts[2].split()
|
||||
if codes and codes[0].isdigit() and 500 <= int(codes[0]) <= 599:
|
||||
five += 1
|
||||
out = [f"TOTAL {total}"]
|
||||
for ip, c in top:
|
||||
out.append(f"TOP {ip} {c}")
|
||||
out.append(f"5XX {five}")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def compare(path, lines, label):
|
||||
rc, out, err = run(path)
|
||||
exp = expected(lines)
|
||||
got = out.strip("\n")
|
||||
if got != exp:
|
||||
print(f"--- ожидалось ({label}) ---\n{exp}\n--- получено ---\n{got}")
|
||||
check(rc == 0, f"{label}: код возврата 0 (получен {rc})", err[:300])
|
||||
check(got == exp, f"{label}: вывод совпадает с эталоном")
|
||||
|
||||
|
||||
def main():
|
||||
fixture = os.path.join(HERE, "access.log")
|
||||
with open(fixture, encoding="utf-8") as f:
|
||||
lines = f.read().splitlines()
|
||||
compare(fixture, lines, "фикстура")
|
||||
|
||||
# пустой файл
|
||||
empty = os.path.join(HERE, "empty.log")
|
||||
open(empty, "w").close()
|
||||
compare(empty, [], "пустой файл")
|
||||
|
||||
# большой лог: 200k строк, часть 5xx, один лидер, ничья между двумя IP
|
||||
rng = random.Random(7)
|
||||
big = os.path.join(HERE, "big.log")
|
||||
ips = ["10.0.0.1"] * 50000 + ["10.0.0.2"] * 30000 + ["10.0.0.3"] * 30000
|
||||
ips += [f"192.168.1.{i}" for i in range(1, 61)] * 10
|
||||
ips += [f"172.16.0.{i}" for i in range(1, 21)] * 5
|
||||
rng.shuffle(ips)
|
||||
big_lines = []
|
||||
for ip in ips:
|
||||
code = rng.choice([200, 200, 200, 301, 404, 500, 502, 503])
|
||||
big_lines.append(f'{ip} - - [23/Sep/2026:11:00:00 +0700] "GET /p HTTP/1.1" {code} 100')
|
||||
with open(big, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(big_lines) + "\n")
|
||||
compare(big, big_lines, "большой лог 110k строк")
|
||||
|
||||
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
|
||||
sys.exit(1 if failures else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user