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

Что это такое

Входной поток, поток вывода и отдельный поток для ошибок есть у каждой команды в конвейере, а труба соединяет вывод одной команды со вводом следующей. Фильтром называют команду, которая читает текст и пишет текст преобразованный, и сила оболочки берётся не из какого-то одного инструмента, а из комбинирования фильтров. Вторая половина модели — статус завершения, то есть нуль при успехе и не нуль при неудаче; именно он позволяет командам принимать решения.

Почему это важно

Стиль композиции — причина, по которой оболочка пережила все попытки её заменить. Новому графическому инструменту приходится реализовывать каждую функцию; новому фильтру командной строки достаточно делать одну вещь, и он сразу сочетается со всем остальным. Чтение конвейера точно говорит, что было сделано с данными и в каком порядке, поэтому конвейер оказывается и документацией, и операцией одновременно. Как только это щёлкнет, однострочники оболочки перестанут выглядеть заклинаниями и начнут выглядеть короткими программами.

Ключевые идеи

Три потока, каждый делает одно

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

sort names.txt > sorted.txt      # перезапись
sort names.txt >> history.txt    # дописывание
grep error app.log 2> err.txt    # ошибки в файл, совпадения на экран

Труба передаёт вывод одной команды следующей

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

cut -d, -f2 orders.csv | sort | uniq -c | sort -rn | head -5

Словарь фильтров невелик и универсален

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

grep -i "error" app.log | wc -l        # посчитать совпавшие строки
awk -F, '{ sum += $3 } END { print sum }' sales.csv   # просуммировать столбец
sed 's/old-name/new-name/g' file.txt  # заменить все вхождения

Коды возврата превращают команды в условия

Успех команда обозначает нулём, а неудачу — чем угодно другим, поэтому такое же соглашение встречается в каждом языке, который её оборачивает. На этом оболочка строит цепочки: следующая команда запускается, только если эта сработала, или, наоборот, только если она провалилась. Скрипты и конвейеры CI собраны ровно из этого, поэтому хорошо воспитанная программа обязана возвращать осмысленный статус, а не преуспевать всегда.

make build && run-tests || echo "сборка или тесты провалились"
grep -q "ERROR" app.log; echo "код возврата $?"

Композиция с переменными и подстановкой команд

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

today=$(date +%F)
tar -czf "backup-$today.tar.gz" ./data

Практика

Соберите отчёт одним конвейером

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

Готово, когда: итоговый конвейер печатает пять строк, где в каждой значение и его количество, а вы можете назвать вклад каждого этапа.

Сделайте команду с двумя потоками

Сделайте так: вывод первой команды вы сохраняете, а её ошибки уходят в файл; затем запустите вторую команду, и выполнится она только в случае успеха первой.

Готово, когда: ошибки лежат в файле, успешный вывод дошёл до экрана, а условная команда повела себя так, как требовал её статус завершения.

Частые ошибки

  • Сборка длинного конвейера за один раз — ошибка в любом месте даёт уверенный неверный ответ; исправить: добавляйте этапы по одному и проверяйте промежуточный результат.
  • Игнорирование кодов возврата — пустой вывод упавшей команды читается как достоверный результат; исправить: связывайте шаги через && или проверяйте статус явно.
  • Переменные без кавычек — значение с пробелом распадается на два аргумента, и команда тихо делает не то; исправить: заключайте в кавычки каждое раскрытие переменной.
  • Путаница перезаписи и дописывания — > уничтожает прежнее содержимое файла; исправить: сначала посмотрите на оператор перенаправления, а потом запускайте команду.
  • Однострочник, которому нужен абзац для объяснения — следующий читатель не сможет безопасно его изменить; исправить: положите его в скрипт с именем и комментарием.

Что читать дальше