Конвейеры и фильтры
Что это такое
Входной поток, поток вывода и отдельный поток для ошибок есть у каждой команды в конвейере, а труба соединяет вывод одной команды со вводом следующей. Фильтром называют команду, которая читает текст и пишет текст преобразованный, и сила оболочки берётся не из какого-то одного инструмента, а из комбинирования фильтров. Вторая половина модели — статус завершения, то есть нуль при успехе и не нуль при неудаче; именно он позволяет командам принимать решения.
Почему это важно
Стиль композиции — причина, по которой оболочка пережила все попытки её заменить. Новому графическому инструменту приходится реализовывать каждую функцию; новому фильтру командной строки достаточно делать одну вещь, и он сразу сочетается со всем остальным. Чтение конвейера точно говорит, что было сделано с данными и в каком порядке, поэтому конвейер оказывается и документацией, и операцией одновременно. Как только это щёлкнет, однострочники оболочки перестанут выглядеть заклинаниями и начнут выглядеть короткими программами.
Ключевые идеи
Три потока, каждый делает одно
Читает команда из стандартного ввода, пишет результаты в стандартный вывод, а диагностику — в стандартный поток ошибок. Разделение ошибок означает вот что: результаты можно направить по трубе куда угодно, а ошибки всё равно дойдут до терминала — ровно то, что нужно, когда длинная задача выдаёт результат. Перенаправление отправляет поток в файл, и разница между перезаписью и дописыванием — один символ.
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 и получите пять самых частых значений одного столбца вместе с числом рядом с каждым. Начинайте от файла, добавляя по одному фильтру и запуская конвейер после каждого шага.
Готово, когда: итоговый конвейер печатает пять строк, где в каждой значение и его количество, а вы можете назвать вклад каждого этапа.
Сделайте команду с двумя потоками
Сделайте так: вывод первой команды вы сохраняете, а её ошибки уходят в файл; затем запустите вторую команду, и выполнится она только в случае успеха первой.
Готово, когда: ошибки лежат в файле, успешный вывод дошёл до экрана, а условная команда повела себя так, как требовал её статус завершения.
Частые ошибки
- Сборка длинного конвейера за один раз — ошибка в любом месте даёт уверенный неверный ответ; исправить: добавляйте этапы по одному и проверяйте промежуточный результат.
- Игнорирование кодов возврата — пустой вывод упавшей команды читается как достоверный результат; исправить: связывайте шаги через
&&или проверяйте статус явно. - Переменные без кавычек — значение с пробелом распадается на два аргумента, и команда тихо делает не то; исправить: заключайте в кавычки каждое раскрытие переменной.
- Путаница перезаписи и дописывания —
>уничтожает прежнее содержимое файла; исправить: сначала посмотрите на оператор перенаправления, а потом запускайте команду. - Однострочник, которому нужен абзац для объяснения — следующий читатель не сможет безопасно его изменить; исправить: положите его в скрипт с именем и комментарием.
Что читать дальше
- Файлы, права и окружение (01-cs-fundamentals/13-unix-shell/02-files-permissions-and-environment) — где эти команды читают и пишут, и к чему им позволено прикасаться.
- Интерфейс командной строки (01-cs-fundamentals/24-command-line-interface) — привычки, благодаря которым эта беглость держится день ото дня.
- Операционные системы (01-cs-fundamentals/15-operating-systems) — потоки, процессы и сигналы, на которых стоят эти трубы.