Конвейеры и фильтры — Примеры

Пример 1 — Сборка отчёта по одному этапу за раз

В файле журнала одна строка хранит один запрос, и седьмое её поле, разделённое пробелами, даёт эндпоинт. Нужны пять самых частых эндпоинтов с их количеством, причём ошибки не должны попасть в результат. Этапы ниже запускаются по одному, и про каждый нужно сказать, что он добавляет, а ещё объяснить, почему ошибки были отделены.

cut -d' ' -f7 access.log           # один эндпоинт на строку
cut -d' ' -f7 access.log | sort    # одинаковые эндпоинты становятся соседями
... | uniq -c                      # считает каждый блок одинаковых строк
... | sort -rn                     # самые частые первыми
... | head -5                      # пять запрошенных

Решение

cut -d' ' -f7 access.log 2> errors.txt \
  | sort | uniq -c | sort -rn | head -5

sort      сближает одинаковые эндпоинты, чтобы uniq -c мог посчитать блок
uniq -c   требует отсортированного ввода; на неотсортированном он даёт много мелких счётчиков
sort -rn  упорядочивает по числу, которое напечатал uniq
2> errors.txt  держит диагностику вне конвейера

Каждый этап делает одно небольшое преобразование, поэтому сборка слева делает неверное поле или флаг видимыми уже после первой команды, а не погребёнными в уверенном итоговом ответе. Сортировка перед подсчётом — тот шаг, который обычно пропускают: uniq схлопывает только соседние одинаковые строки, поэтому на неотсортированном вводе один эндпоинт попадает в отчёт несколько раз с мелкими счётчиками. Ловушка здесь в трёх потоках: если бы диагностика делила стандартный вывод, в подсчёт попали бы мусорные строки.

Пример 2 — Сбой, который выглядел как пустой результат

Скрипт считает сегодняшние ошибки и печатает их число. В запуске ниже появился 0, хотя файла журнала на самом деле не было. Скажите, откуда взялся нуль, и перепишите скрипт так, чтобы сбой нельзя было прочитать как достоверный результат.

count=$(grep -c "ERROR" /var/log/app.log)
echo "ошибок: $count"
# ошибок: 0        <- файла не существует

Решение

if count=$(grep -c "ERROR" /var/log/app.log); then
  echo "ошибок: $count"
else
  echo "не удалось прочитать журнал (код $?)" >&2
  exit 1
fi

Успех команда отмечает нулевым статусом завершения, а неудачу — любым другим; grep в этом примере провалился, но всё равно записал 0 в стандартный вывод, поэтому отличить спокойный день от файла, которого нет, скрипт не мог. Неоднозначность снимает проверка статуса: путь сбоя говорит, что именно не удалось прочитать, и завершается ненулём, чтобы это заметил тот, кто вызвал скрипт. Ловушка же в том, что число выглядит совершенно достоверным, поэтому неисправность всплывает гораздо позже — как дашборд, который тихо показывал нуль.