Скрипты и обработка логов
Конвейеры shell и анализ логов через awk, sed, sort и uniq, а также алгоритмические задачи на логах.
14 вопросов
JuniorТеорияЧастоЧто делает set -euo pipefail по флагам и зачем это нужно?
Что делает set -euo pipefail по флагам и зачем это нужно?
Это ужесточает поведение скрипта при сбоях. -e завершает работу на первой команде с ненулевым кодом; -u считает обращение к неустановленной переменной ошибкой, а не пустой строкой; -o pipefail роняет конвейер, если падает ЛЮБАЯ его стадия, а не только последняя. Без pipefail cmd1 | cmd2 маскирует сбой cmd1, если cmd2 отработал успешно.
Типичные ошибки
- ✗Верить, что один
set -eловит сбой в середине конвейера - ✗Ожидать, что
-uошибётся на уже установленной переменной, а не на неустановленной - ✗Думать, что
pipefailотдаёт статус последней стадии, а не худшей
Уточняющие вопросы
- →Почему
set -eвсё равно пропускает сбой внутриif,&&или подстановки команд? - →Как добавление
-o pipefailменяет код возвратаgrep x file | head?
MiddleКодЧастоДобавьте надёжную обработку ошибок и очищающий trap в хрупкий скрипт деплоя
Добавьте надёжную обработку ошибок и очищающий trap в хрупкий скрипт деплоя
Включите строгий режим через set -euo pipefail, чтобы упавшая команда, сломанная стадия конвейера или неустановленная переменная прерывали работу. Сразу после mktemp повесьте trap 'rm -f "$tmp"' EXIT: ловушка EXIT срабатывает при нормальном завершении, при аварийном выходе и при пойманном сигнале, поэтому временный файл всегда удаляется.
Типичные ошибки
- ✗Вешать
trapдоmktemp, из-за чего ранний сбой не запускает очистку - ✗Верить, что один
set -eсам удаляет временные файлы безtrap - ✗Ставить trap лишь на
SIGINTилиRETURN, пропуская путь аварийного выхода
Уточняющие вопросы
- →Почему вешать
trapнаEXIT, а не наSIGINTиSIGTERMпо отдельности? - →Как ведёт себя
set -eдля команды, чей сбой вы сознательно допускаете?
JuniorДебаггингИногдаЭтот скрипт бэкапа тихо архивирует не те пути — как отладить и исправить?
Этот скрипт бэкапа тихо архивирует не те пути — как отладить и исправить?
Запустите его через bash -x script (или добавьте set -x) — и видно, как tar разворачивается в /srv/My и App как отдельные аргументы: неэкранированный $paths дробится по каждому пробелу. shellcheck помечает это как SC2086. Исправление — хранить пути в массиве и передавать "${paths[@]}", что оставляет каждый путь одним аргументом.
Типичные ошибки
- ✗Считать, что несколько путей безопасно умещаются в одну переменную через пробел
- ✗Не запускать
bash -xилиshellcheck, из-за чего тихий word-split остаётся незамеченным - ✗Оставлять
$pathsбез кавычек, из-за чегоMy Appделится на два аргумента
Уточняющие вопросы
- →О чём предупреждает код
shellcheckSC2086 и как его правильно заглушить? - →Почему
set -xвскрывает баг word-splitting, который не виден при чтении исходника?
JuniorТеорияИногдаЧто в bash-скрипте означают $0, $1, $@ и $#?
Что в bash-скрипте означают $0, $1, $@ и $#?
$0 — имя или путь самого скрипта; $1…$9 (и ${10} дальше) — позиционные аргументы; $# — их количество; $@ — все они. Всегда используйте "$@" в двойных кавычках: он раскрывается в каждый аргумент отдельным словом, сохраняя аргументы с пробелами, тогда как $* склеивает их в одну строку.
Типичные ошибки
- ✗Писать
$@без кавычек, из-за чего аргументы с пробелами разбиваются на несколько слов - ✗Путать
$#(число аргументов) с$0(имя скрипта) - ✗Считать, что
$*и"$@"ведут себя одинаково при передаче аргументов
Уточняющие вопросы
- →Почему
"$@"сохраняет аргумент вродеtwo words, а$*— нет? - →Как сдвигать позиционные параметры через
shift, чтобы обработать их в цикле?
JuniorТеорияИногдаКак работают переменные bash — $var против ${var}, подстановка команд и кавычки?
Как работают переменные bash — $var против ${var}, подстановка команд и кавычки?
$var и ${var} оба раскрывают переменную; фигурные скобки лишь ограничивают имя, как в ${x}_bak. Подстановка команд $(cmd) захватывает stdout команды в значение. Ключевое правило: неэкранированный $var проходит word-splitting и раскрытие glob, поэтому значение с пробелами превращается в несколько аргументов — всегда пишите "$var".
Типичные ошибки
- ✗Оставлять
$varбез кавычек, из-за чего значение с пробелами или glob дробится на много аргументов - ✗Думать, что
${var}отличается по смыслу от$var, а не просто ограничивает имя - ✗Путать подстановку команд
$(cmd)с арифметическим раскрытием$((expr))
Уточняющие вопросы
- →Когда
${var}строго обязательно, а не просто желательно? - →Чем
"$@"отличается от$*при передаче аргументов дальше?
JuniorТеорияИногдаЧто такое код возврата, что хранит $? и как && и || строят цепочки?
Что такое код возврата, что хранит $? и как && и || строят цепочки?
Каждая команда возвращает код возврата: 0 — успех, любой ненулевой (1–255) — неудача. $? хранит код возврата последней выполненной команды. a && b запускает b, только если a успешна (код 0); a || b запускает b, только если a упала (ненулевой). Так mkdir d && cd d заходит в каталог, лишь когда создание удалось.
Типичные ошибки
- ✗Читать
0как неудачу — в оболочках0это успех, а ненулевой — неудача - ✗Проверять
$?после промежуточной команды, которая его уже перезаписала - ✗Менять местами
&&и||—&&идёт на успехе,||на неудаче
Уточняющие вопросы
- →Почему
$?нужно захватывать сразу, до запуска любой другой команды? - →Как
set -eвзаимодействует с командой в цепочкеa && b?
JuniorТеорияИногдаЧто делает конвейер shell и что вносят awk, sort и uniq по отдельности?
Что делает конвейер shell и что вносят awk, sort и uniq по отдельности?
Конвейер соединяет команды через |, направляя stdout одной команды в stdin следующей, так что каждая делает одно дело. awk извлекает и преобразует поля по колонке или шаблону; sort упорядочивает строки, с -n численно и -h по-человечески; uniq схлопывает соседние дубликаты строк, а -c их считает. Классика awk | sort | uniq -c извлекает поле, группирует его и считает вхождения.
Типичные ошибки
- ✗Забывать, что
uniqсхлопывает лишь соседние дубликаты, поэтому нужна сортировка до него - ✗Путать, что делает каждый из
awk,sortиuniq - ✗Думать, что конвейер буферизует во временный файл, а не передаёт stdout в stdin потоком
Уточняющие вопросы
- →Почему нужно
sortпередuniq -c, чтобы получить верные счётчики по значению? - →Как добавить
sort -rnв конце, чтобы отранжировать подсчитанные значения?
MiddleКодИногдаС помощью awk просуммируйте и усредните столбец, отфильтровав строки по полю
С помощью awk просуммируйте и усредните столбец, отфильтровав строки по полю
Фильтруйте шаблоном, накапливайте в теле, печатайте в END: awk '$9==200 { sum+=$10; n++ } END { if (n) print sum, sum/n }' access.log. Шаблон $9==200 запускает блок лишь на подходящих строках, sum+=$10 суммирует поле байтов, n++ их считает, а END срабатывает по концу ввода. Проверка n не даёт делить на ноль.
Типичные ошибки
- ✗Писать
$9=200(присваивание, всегда истина) вместо$9==200(сравнение) - ✗Делить на
NR(все строки ввода), а не на отфильтрованный счётчикn - ✗Печатать в теле на каждой строке, а не один раз в блоке
END
Уточняющие вопросы
- →Почему
$9=200совпадает с каждой строкой, а$9==200фильтрует верно? - →Как сгруппировать итоги по каждому коду статуса, а не только по
200?
MiddleКодИногдаБезопасно обойдите файлы, когда в именах бывают пробелы или переводы строк
Безопасно обойдите файлы, когда в именах бывают пробелы или переводы строк
for f in $(find …) небезопасен: подстановка проходит word-splitting по IFS, поэтому путь с пробелом или переводом строки распадается на итерации, а glob в имени раскрывается. Обходите NUL-разделённый вывод: find … -print0 | while IFS= read -r -d '' f; do …; done. Символ \0 не встречается в имени, а IFS=/-r сохраняют имя дословно.
Типичные ошибки
- ✗Обходить через
for f in $(find …)или$(ls), что дробит по каждому пробелу - ✗Закавычивать всю подстановку — тогда выходит одно огромное единое значение
- ✗Опускать
IFS=или-r, из-за чегоreadрежет пробелы или ест обратные слэши
Уточняющие вопросы
- →Почему
-print0идёт в паре сread -d '', а не с разделителем-переводом строки? - →Чем
find -exec gzip {} +отличался бы от циклаwhile readздесь?
MiddleКодИногдаНаправьте stdout и stderr в один лог — ловушка порядка 2>&1
Направьте stdout и stderr в один лог — ловушка порядка 2>&1
Перенаправления применяются слева направо, поэтому myjob 2>&1 > run.log сначала направляет stderr туда, где сейчас stdout (в терминал), а затем уводит stdout в файл — ошибки утекают. Ставьте файл первым: myjob > run.log 2>&1. Ошибки убирают через 2>/dev/null; stdin подают here-doc'ом cmd <<'EOF' … EOF (тег в кавычках — без подстановки).
Типичные ошибки
- ✗Писать
2>&1до файла, из-за чего stderr копирует терминал, а не файл - ✗Думать, что
2>&1меняет дескрипторы местами, а не дублирует один в другой - ✗Верить, что оболочка разрешает перенаправления разом, а не слева направо
Уточняющие вопросы
- →Что делает сокращение
&>(или&>>) и почему предпочитать явную форму? - →Почему тег here-doc в кавычках
<<'EOF'отключает подстановку переменных внутри?
MiddleКодИногдаПодсчёт уникальных IP в access-логе, где IP — первое поле
Подсчёт уникальных IP в access-логе, где IP — первое поле
Извлеките первое поле, схлопните до различных значений и сосчитайте: awk '{print $1}' access.log | sort -u | wc -l. sort -u убирает дубликаты за один проход, а wc -l считает оставшиеся строки; ... | sort | uniq | wc -l равнозначно. Для счётчиков по IP используйте sort | uniq -c | sort -rn. Стоимость определяется сортировкой за O(n log n).
Типичные ошибки
- ✗Применять
uniqбез предшествующегоsort, из-за чего несоседние дубликаты выживают - ✗Считать сырые строки вместо различных значений первого поля
- ✗Извлекать не то поле — IP это
$1, а не$2
Уточняющие вопросы
- →Как изменить конвейер, чтобы вывести 10 самых активных IP?
- →Почему
sort -uздесь равнозначенsort | uniqи когда они расходятся?
MiddleКодИногдаЧто выведет двухступенчатый конвейер awk поверх ps -ef?
Что выведет двухступенчатый конвейер awk поверх ps -ef?
Выведется столбец PID. Первый awk склеивает поле 2 (PID) и поле 8 (CMD) как PID:CMD. Второй awk -F: разбивает строку по : и печатает $1 — часть до первого двоеточия, то есть PID. Так как команда может содержать двоеточия, границей служит лишь первое, поэтому $1 остаётся PID. Например 1234:/usr/bin/sshd схлопывается в 1234.
Типичные ошибки
- ✗Сбиваться в нумерации полей
ps -ef—$2это PID, а не PPID - ✗Думать, что
-F:оставляет часть после двоеточия, а не$1до него - ✗Забывать, что разбивает лишь первое
:, так что команда с двоеточиями безопасна
Уточняющие вопросы
- →Как изменить конвейер, чтобы вывести команду вместо PID?
- →Почему передавать вывод
psвawkненадёжно, если значение столбца содержит пробелы?
MiddleКодИногдаКогда переписывать хрупкий bash-конвейер на Python и как это сделать?
Когда переписывать хрупкий bash-конвейер на Python и как это сделать?
Переходите на Python, когда задача перерастает построчный текст — структурированный вывод (JSON), вложенные данные, обработка ошибок или тестируемая логика. Здесь самодельный JSON (хвостовая запятая, без экранирования) — верный признак. Перепишите: читайте лог, копите в dict/Counter и выводите через json.dumps — кавычки и структура тогда корректны.
Типичные ошибки
- ✗Собирать JSON конкатенацией строк вместо
json.dumps - ✗Считать bash всегда верным выбором, потому что он короче в наборе
- ✗Игнорировать, что незакавыченные поля и голый
grepломаются подpipefail
Уточняющие вопросы
- →Почему
json.dumpsбезопаснее сборки JSON-строки поле за полем? - →Как
grepбез совпадений взаимодействует сset -o pipefail?
MiddleКодИногдаНайти K самых частых IP в огромном логе за O(n log K)
Найти K самых частых IP в огромном логе за O(n log K)
Сосчитайте частоты в хеш-таблице за один проход O(N), затем держите min-heap размера K по счётчикам: добавляйте каждую пару (count, ip), и как только куча превысит K — извлекайте наименьший. Каждая операция кучи O(log K), так что отбор по U различным IP — это O(U log K) ≤ O(N log K). В куче остаются K наибольших; отсортируйте лишь эти K для итогового порядка. Это лучше сортировки всех уникальных за O(U log U).
Открыть задачу →Типичные ошибки
- ✗Брать max-heap размера K вместо min-heap — извлекать нужно наименьший
- ✗Сортировать все уникальные IP (O(U log U)), когда нужны лишь top-K
- ✗Забывать про проход подсчёта O(N) и пытаться класть в кучу сырые строки
Уточняющие вопросы
- →Почему хранение K наибольших требует min-heap, а не max-heap?
- →Как адаптировать это, когда даже счётчики различных IP не влезают в память?