Как перенаправить stderr, а не stdout?
У меня есть программа, которая выводит информацию в stdout
и stderr
, и мне нужно обработать stderr
с помощью grep
, игнорируя stdout
.
Используя временный файл, это можно сделать в два этапа:
command > /dev/null 2> temp.file
grep 'something' temp.file
Но как можно сделать это без временных файлов, используя одну команду и каналы?
5 ответ(ов)
Чтобы поменять местами вывод стандартного потока ошибок и стандартного вывода, выполните следующую команду:
command 3>&1 1>&2 2>&3
Данная команда создает новый дескриптор файла (3) и присваивает ему то же место, что и дескриптор 1 (стандартный вывод). Затем дескриптор 1 (стандартный вывод) перенаправляется на то же место, что и дескриптор 2 (стандартный поток ошибок), и в конечном итоге дескриптор 2 (стандартный поток ошибок) перенаправляется на то же место, что и дескриптор 3 (стандартный вывод).
Таким образом, стандартный поток ошибок теперь доступен как стандартный вывод, а старый стандартный вывод сохраняется в стандартном потоке ошибок. Возможно, это избыточно, но, надеюсь, это даст больше информации о дескрипторах файлов в Bash (в каждом процессе доступно девять таких дескрипторов).
Комбинируя лучшее из этих ответов, если вы выполните команду:
command 2> >(grep -v something 1>&2)
то весь вывод stdout будет сохранён как stdout, а весь вывод stderr будет сохранён как stderr, но вы не увидите ни одной строки в stderr, содержащей строку "something".
Эта команда имеет уникальное преимущество в том, что она не меняет порядок вывода stdout и stderr, не объединяет их и не использует временные файлы.
Проще всего визуализировать работу с перенаправлениями и пайпами в bash, если понять, что на самом деле происходит с файловыми дескрипторами 0, 1 и 2 (можно посмотреть в /proc/[pid]/fd/*).
Когда в командной строке присутствует оператор пайпа или "|", сначала bash создает fifo и перенаправляет FD 1 (стандартный вывод) команды слева на этот fifo, а FD 0 (стандартный ввод) команды справа – на тот же fifo.
Далее операторы перенаправления для каждой стороны обрабатываются слева направо, и текущие настройки используются каждый раз, когда происходит дублирование дескриптора. Это важно, потому что, так как пайп первым был настроен, FD1 (слева) и FD0 (справа) уже изменены по сравнению с тем, что они могли бы быть в обычной ситуации, и любое дублирование этих дескрипторов отразит этот факт.
Таким образом, когда вы вводите что-то вроде следующего:
command 2>&1 >/dev/null | grep 'something'
Происходит следующее, по порядку:
- создается пайп (fifo); FD1 команды "command" указывает на этот пайп, а FD0 команды "grep" также указывает на него.
- FD2 команды "command" указывает на то, на что сейчас указывает FD1 (пайп).
- FD1 команды "command" указывает на /dev/null.
Таким образом, весь вывод, который "command" записывает в свой FD2 (stderr), попадает в пайп и считывается "grep" с другой стороны. Весь вывод, который "command" записывает в свой FD1 (stdout), попадает в /dev/null.
Если вместо этого вы выполните следующее:
command >/dev/null 2>&1 | grep 'something'
Происходит следующее:
- создается пайп, и FD1 команды "command" и FD0 команды "grep" указывают на него.
- FD1 команды "command" указывает на /dev/null.
- FD2 команды "command" указывает на то, на что сейчас указывает FD1 (/dev/null).
Таким образом, весь stdout и stderr от "command" попадают в /dev/null. Ничто не поступает в пайп, и, следовательно, "grep" завершится без отображения чего-либо на экране.
Также стоит отметить, что перенаправления (файловые дескрипторы) могут быть только для чтения (<), только для записи (>) или для чтения и записи (<>) .
И напоследок, стоит упомянуть, что то, куда программа записывает данные — в FD1 или FD2, полностью зависит от программиста. Хорошая практика программирования предполагает, что сообщения об ошибках должны выводиться в FD2, а нормальный вывод — в FD1, однако вы часто можете столкнуться с неаккуратным кодом, который смешивает оба типа или игнорирует эту конвенцию.
Если вы используете Bash, то можно воспользоваться такой конструкцией:
command >/dev/null |& grep "something"
Здесь >/dev/null
сначала перенаправляет stdout
в /dev/null
, а затем |&
перенаправляет stderr
в stdout
.
Таким образом, вы оставляете stderr
перенаправленным в stdout
, а оригинальный stdout
отсекается.
Аналогичный результат можно получить с помощью:
command >/dev/null 2>&1 | grep "something"
Однако, по моему опыту, |&
может захватывать дополнительный вывод, который не перенаправляется только с помощью 2>&1
, например, при запуске определенных процессов под Wine (которые могут включать несколько подсистем и перенаправлений). Это, похоже, нигде не документируется.
Чтобы постоянно перенаправить stdout
и stderr
в файлы, при этом производя фильтрацию stderr
с помощью grep
, но оставляя stdout
для вывода сообщений в терминал (TTY), можно использовать следующий подход:
# Сохраняем дескриптор tty-stdout в fd 3
exec 3>&1
# Перенаправляем stdout и stderr, фильтруем stderr с помощью grep (-v) для удаления ненужных сообщений и добавляем их в файлы
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# это сообщение попадает в std.out
echo "my first message" >&1
# это сообщение попадает в std.err
echo "a error message" >&2
# это сообщение никуда не попадет
echo "this nasty_msg won't appear anywhere" >&2
# это сообщение выводится в tty
echo "a message on the terminal" >&3
Таким образом, мы можем управлять выводом сообщений в файлы и на экран, исключая определенные сообщения из stderr
, что позволяет более гибко справляться с логированием и наблюдать за ошибками, не загромождая вывод.
Как перенаправить вывод в файл и на экран (stdout)
Как использовать 'grep' для непрерывного потока?
Извлечение имени файла и расширения в Bash
Как работает "cat << EOF" в bash?
Передача параметров в функцию Bash