Конвейеры и перенаправление (pipes)
| Идея конвейеров | Дуглас Макилрой (1964) |
|---|---|
| Реализация в Unix | Кен Томпсон, Unix Version 3 (1973) |
| Стандартные потоки | 0 — stdin, 1 — stdout, 2 — stderr |
| Операторы | |, >, >>, <, 2>, 2>&1, &>, <<, <<< |
| Связанные механизмы | tee, именованные каналы (mkfifo), подстановка процессов |
| Принцип | маленькие программы, каждая делает одно дело |
Конвейер (англ. pipe, «труба», в жаргоне — «пайп») — механизм Unix, который соединяет стандартный вывод одной программы со стандартным вводом другой: команда1 | команда2. Перенаправление связывает потоки ввода-вывода с файлами: команда > out.txt. Вместе они воплощают философию Unix: вместо одной большой программы, делающей всё, — маленькие утилиты, каждая из которых делает одно дело хорошо, соединяемые в цепочки.
Конвейеры — основа повседневной работы в командной строке Linux: найти в логе ошибки, отсортировать, посчитать, взять первые десять — одной строкой.
История
Идею соединять программы «как садовые шланги» высказал Дуглас Макилрой в служебной записке Bell Labs ещё в 1964 году. В 1973 году Кен Томпсон за одну ночь добавил в Unix системный вызов pipe() и синтаксис с вертикальной чертой, переписав утилиты так, чтобы они читали стандартный ввод и писали в стандартный вывод. Этот момент часто называют рождением «философии Unix».
Стандартные потоки
Каждый процесс начинает работу с тремя открытыми потоками — файловыми дескрипторами:
| Номер | Имя | Назначение | По умолчанию |
|---|---|---|---|
| 0 | stdin | стандартный ввод | клавиатура |
| 1 | stdout | стандартный вывод | экран |
| 2 | stderr | поток ошибок | экран |
Разделение вывода и ошибок важно: при команда > file в файл уходят данные, а сообщения об ошибках по-прежнему видны на экране и не портят результат.
Перенаправления
ls > files.txt # stdout в файл (перезаписать)
ls >> files.txt # дописать в конец
sort < names.txt # stdin из файла
make 2> errors.log # только stderr в файл
cmd > out.log 2>&1 # оба потока в один файл (порядок важен!)
cmd &> out.log # то же, сокращение Bash
cmd 2>/dev/null # выбросить ошибки
cmd > /dev/null 2>&1 # выбросить всё
Запись 2>&1 означает «направить поток 2 туда же, куда сейчас направлен поток 1». Поэтому cmd 2>&1 > file отправит ошибки на экран, а в файл — только вывод: перенаправления применяются слева направо.[2]
Here-документ и here-строка
cat <<EOF > config.ini
[server]
host = $HOSTNAME
port = 8080
EOF
grep -c "a" <<< "banana" # строка как stdin
Внутри <<EOF переменные раскрываются; чтобы передать текст буквально, ограничитель берут в кавычки: <<'EOF'.
Конвейеры
cat access.log | grep " 500 " | wc -l # число ошибок 500
ps aux | sort -k4 -rn | head -5 # 5 процессов, потребляющих больше всего памяти
journalctl -u nginx --since today | grep -i error | less
du -sh * | sort -h | tail # самые большие каталоги
curl -s https://api.github.com/repos/python/cpython | jq '.stargazers_count'
Программы конвейера запускаются одновременно, и данные текут между ними порциями через буфер ядра (обычно 64 КБ в Linux), не накапливаясь целиком.[3] Если следующая программа читает медленнее, предыдущая ждёт; если она завершилась (как head), предыдущая получает сигнал SIGPIPE и тоже завершается (см. Процессы и сигналы). Поэтому head по огромному файлу работает мгновенно.
Коды выхода
Код выхода конвейера по умолчанию — код последней команды: false | true считается успехом. В скриптах это скрывает ошибки, поэтому используют set -o pipefail — тогда конвейер завершается неуспешно, если упала любая из команд. Коды всех команд доступны в массиве PIPESTATUS.[1]
Полезные приёмы
teeраздваивает поток: пишет и в файл, и дальше по конвейеру —make 2>&1 | tee build.log. Запись в файл с правами root:echo "..." | sudo tee /etc/file.- Подстановка процессов
<(команда)подставляет вывод команды как временный файл:diff <(sort a.txt) <(sort b.txt). - Подстановка команд
$(команда)вставляет вывод в строку:kill $(pgrep -f worker). - Именованные каналы (
mkfifo /tmp/pipe) — конвейер в виде файла, через который могут общаться независимые процессы.[5] xargsпревращает поток строк в аргументы команды (см. find и xargs).
Классический пример
Подсчёт десяти самых частых слов в тексте — задача, на которой Макилрой в 1986 году показал силу конвейеров:
tr -cs '[:alpha:]' '\n' < book.txt | tr '[:upper:]' '[:lower:]' | sort | uniq -c | sort -rn | head
Каждая утилита проста: tr разбивает текст на слова и приводит к нижнему регистру, sort и uniq -c считают повторы, sort -rn упорядочивает, head берёт верхушку. Те же блоки решают и десятки других задач — для полей и сумм удобен awk, для поиска — grep, для JSON — jq.
Ограничения
Конвейеры передают неструктурированный текст: каждая программа заново разбирает строки, и пробелы в именах файлов или неожиданный формат вывода ломают цепочку. Для сложной логики и структурированных данных лучше подходят Python или оболочки нового поколения, передающие объекты (PowerShell, Nushell). Синтаксис конвейеров и перенаправлений закреплён в стандарте POSIX и одинаково работает в Bash, Zsh и dash.[4]