Все главы учебника
Содержание учебника
Глава 05 / Linux

Потоки текста, конвейеры и коды завершения

10 мин чтенияКонтент v0.10.0

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

В прошлом уроке find выбирал файлы. Теперь содержимое становится входом программы. Будем работать с маленьким учебным журналом, где формат заранее известен. Такие конвейеры полезны для проверки гипотез, но они не заменяют полноценный разбор произвольного JSON или CSV с кавычками.

Три стандартных канала

У процесса обычно есть стандартный ввод, стандартный вывод и стандартный поток ошибок: stdin, stdout, stderr. Файловые дескрипторы 0,1,2 обозначают эти каналы. В терминале ввод идёт с клавиатуры, оба вывода видны рядом. Перенаправление позволяет отправить их в разные места.

lab_dir=$(mktemp -d)
cd "$lab_dir"
printf '200 /home\n500 /pay\n200 /home\n404 /old\n500 /pay\n' > requests.log
cat requests.log

> открывает файл для перезаписи ещё до выполнения программы. >> добавляет в конец. Поэтому конструкция sort file > file опасна: shell может сначала опустошить исходник. Записывайте результат в другое имя и только после проверки решайте о замене.

cat missing.log > result.txt 2> errors.txt
cat errors.txt

Здесь результат может быть пустым, а сообщение об отсутствии файла находится в errors.txt. Точная формулировка зависит от языка системы. 2> перенаправляет stderr, но не делает ошибку успешным чтением. Файл результата может существовать даже после неудачи.

Соединяем программы

Символ | подключает stdout слева к stdin справа. Программы обычно работают параллельно: не требуется заранее сохранить весь промежуточный результат. Если читатель медленнее, заполненный буфер канала может приостановить писателя. Это причина не считать конвейер бесконечной бесплатной памятью.

grep '^500 ' requests.log
awk '{print $2}' requests.log | sort | uniq -c

Первый вывод содержит две строки 500 /pay. Во втором /home и /pay имеют по два появления, /old — одно. uniq объединяет соседние одинаковые строки, поэтому для общего подсчёта перед ним нужна сортировка. Порядок вывода зависит от сортировки, локали и выбранных опций; для воспроизводимого простого ASCII-порядка можно задать LC_ALL=C конкретной команде.

grep ищет по шаблону. Здесь ^ привязывает совпадение к началу строки, а пробел отделяет код от пути. Для буквального фрагмента подходит grep -F. awk делит простую строку на поля и печатает второе. Это объясняет наш формат, но пробел внутри пути или сложные кавычки потребуют другого parser. Не называйте такой пример универсальным анализатором журналов.

Статус — отдельный результат

После команды shell хранит её код завершения в $?. Обычно 0 означает успех, ненулевое значение — другой исход согласно договору программы. Для grep 1 означает отсутствие совпадений, а не обязательно неисправность. Читать $? надо сразу: следующая команда заменит его.

grep '^503 ' requests.log
status=$?
printf 'grep status=%s\n' "$status"

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

По умолчанию Bash возвращает для конвейера статус последней команды. Это иногда скрывает сбой предыдущей:

false | cat
printf 'pipeline=%s\n' "$?"
set -o pipefail
false | cat
printf 'pipeline with pipefail=%s\n' "$?"
set +o pipefail

Первый статус обычно 0, второй 1. Pipefail меняет правила статуса, но не отменяет уже выполненные эффекты и не гарантирует, что последующая программа не начинала работу. Настройка относится к текущему Bash. Другие оболочки могут иметь иной договор. Для интерактивного урока вернули прежнее значение; в собственном скрипте выбор фиксируют явно.

Самостоятельная лаборатория

Сделайте отдельный отчёт только строк 500, посчитайте их количество и сохраните диагностику отсутствующего файла в stderr-файл. Затем соберите список уникальных путей со счётчиками. До запуска предскажите число строк: в исходном журнале пять запросов, две ошибки 500, три разных пути.

Практика занимает около двадцати минут. Критерии готовности: исходный requests.log не изменился; вы не принимаете существование файла результата за успешную команду; понимаете, где находится stderr; объясняете разницу между grep1 и отсутствующим входом. Команды работают с именами только своей лаборатории.

Разбор

grep '^500 ' requests.log > failures.log создаёт две строки. wc -l < failures.log печатает число без имени файла. Подсчёт всех запросов можно проверить тем же способом для исходника. Пара sort→uniq объединяет одинаковые пути независимо от исходного порядка. Если убрать sort, повтор /home между другими строками может остаться отдельной группой.

Проверьте один новый случай: добавьте строку 200 с новым путём. Число запросов увеличится, а число 500 не изменится. Такой маленький контрпример проверяет смысл фильтра лучше, чем длинный конвейер без ожидаемого результата. В следующих главах будем использовать вывод и статусы для проверки прав и работы процессов.

Первичные источники: GNU Bash: pipelines, GNU grep, GNU awk.