Перенаправление потоков это то, без чего в 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-задачи собираются из обычных команд, а скрипты ведут себя одинаково на каждом запуске.