Перенаправление потоков в Linux: stdout, stderr и pipe

Перенаправление потоков в Linux: stdout, stderr и pipe

Перенаправление потоков это то, без чего в Linux не обходится ни один скрипт: вывод команды пишется в файл, ошибки в отдельный лог, результат одной программы уходит на вход другой. Всё это подробно описано в разделе Redirections руководства GNU Bash. Ниже операторы, которые реально нужны каждый день, и примеры к ним.

Три стандартных потока

  • 0, stdin: ввод, по умолчанию клавиатура.
  • 1, stdout: обычный вывод команды.
  • 2, stderr: ошибки и диагностика.

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

Основные операторы

  • > пишет stdout в файл и перезаписывает его.
  • >> дописывает stdout в конец файла.
  • < подаёт файл на stdin команды.
  • 2> и 2>> делают то же самое для stderr.
  • 2>&1 отправляет stderr туда же, куда сейчас смотрит stdout.
  • &> и &>> в Bash отправляют сразу оба потока в файл.
  • | передаёт stdout левой команды на stdin правой.

Запись и дописывание в файл

echo "Привет, мир!" > hello.txt
echo "До свидания, мир!" >> hello.txt

Первая команда создаст файл или перезапишет его, вторая добавит строку в конец. Если боитесь случайно затереть нужный файл одиночной >, включите set -o noclobber: тогда Bash откажется перезаписывать существующий файл, а перезапись придётся писать явно через >|.

Ввод из файла

sort < data.csv
wc -l < access.log

Многие утилиты сами принимают имя файла, но < полезен для программ, которые читают только stdin. А wc -l < файл выводит одно число без имени файла, это удобно в скриптах.

Только ошибки в файл

ls -l /нет_такого_каталога 2> error.log
find / -name "*.conf" 2>/dev/null

В первом случае сообщение об ошибке уйдёт в error.log, а не на экран. Во втором find не будет засыпать терминал сообщениями Permission denied: ошибки улетают в /dev/null, а найденные файлы выводятся как обычно.

stdout и stderr в один лог

./deploy.sh > deploy.log 2>&1
# то же самое короче, только в Bash:
./deploy.sh &> deploy.log

Порядок здесь важен. Запись cmd 2>&1 > file отправит ошибки в терминал, а в файл только stdout: в момент 2>&1 stdout ещё смотрел на терминал. Правильно сначала > file, потом 2>&1. Форма &> работает в Bash и Zsh, а в чистом sh, например в dash на Debian и Ubuntu, её нет, так что в скриптах с #!/bin/sh пишите длинный вариант.

Pipe: вывод одной команды на вход другой

grep "error" /var/log/nginx/error.log | sort | uniq -c | sort -rn | head
journalctl -u nginx --since today |& less

Цепочки из grep, sort и uniq это обычный способ быстро разобрать логи без лишних инструментов. Оператор |& в Bash отправляет в pipe и stdout, и stderr, это сокращение для 2>&1 |.

Чуть глубже

  • /dev/null поглощает всё: cmd > /dev/null 2>&1 полностью глушит вывод, это частый приём в cron.
  • Here-doc, cat <<EOF … EOF, подаёт многострочный текст на stdin прямо из скрипта.
  • Here-string, grep foo <<< “$var”, подаёт на stdin содержимое переменной.
  • Подстановка процессов в Bash, diff <(cmd1) <(cmd2), сравнивает выводы двух команд без временных файлов.
  • set -o pipefail в скриптах: pipe считается упавшим, если упала любая команда в цепочке, а не только последняя.
  • tee пишет поток в файл и одновременно передаёт дальше: cmd | tee out.log.

В итоге хватает запомнить несколько операторов: >, >>, 2>, 2>&1, &> и |, плюс правило, что перенаправления читаются слева направо. С ними логи, отчёты и cron-задачи собираются из обычных команд, а скрипты ведут себя одинаково на каждом запуске.

Источники и ссылки


Комментарии загружаются…