12

Как перенаправить stderr, а не stdout?

17

У меня есть программа, которая выводит информацию в stdout и stderr, и мне нужно обработать stderr с помощью grep, игнорируя stdout.

Используя временный файл, это можно сделать в два этапа:

command > /dev/null 2> temp.file
grep 'something' temp.file

Но как можно сделать это без временных файлов, используя одну команду и каналы?

5 ответ(ов)

4

Чтобы поменять местами вывод стандартного потока ошибок и стандартного вывода, выполните следующую команду:

command 3>&1 1>&2 2>&3

Данная команда создает новый дескриптор файла (3) и присваивает ему то же место, что и дескриптор 1 (стандартный вывод). Затем дескриптор 1 (стандартный вывод) перенаправляется на то же место, что и дескриптор 2 (стандартный поток ошибок), и в конечном итоге дескриптор 2 (стандартный поток ошибок) перенаправляется на то же место, что и дескриптор 3 (стандартный вывод).

Таким образом, стандартный поток ошибок теперь доступен как стандартный вывод, а старый стандартный вывод сохраняется в стандартном потоке ошибок. Возможно, это избыточно, но, надеюсь, это даст больше информации о дескрипторах файлов в Bash (в каждом процессе доступно девять таких дескрипторов).

2

Комбинируя лучшее из этих ответов, если вы выполните команду:

command 2> >(grep -v something 1>&2)

то весь вывод stdout будет сохранён как stdout, а весь вывод stderr будет сохранён как stderr, но вы не увидите ни одной строки в stderr, содержащей строку "something".

Эта команда имеет уникальное преимущество в том, что она не меняет порядок вывода stdout и stderr, не объединяет их и не использует временные файлы.

1

Проще всего визуализировать работу с перенаправлениями и пайпами в bash, если понять, что на самом деле происходит с файловыми дескрипторами 0, 1 и 2 (можно посмотреть в /proc/[pid]/fd/*).

Когда в командной строке присутствует оператор пайпа или "|", сначала bash создает fifo и перенаправляет FD 1 (стандартный вывод) команды слева на этот fifo, а FD 0 (стандартный ввод) команды справа – на тот же fifo.

Далее операторы перенаправления для каждой стороны обрабатываются слева направо, и текущие настройки используются каждый раз, когда происходит дублирование дескриптора. Это важно, потому что, так как пайп первым был настроен, FD1 (слева) и FD0 (справа) уже изменены по сравнению с тем, что они могли бы быть в обычной ситуации, и любое дублирование этих дескрипторов отразит этот факт.

Таким образом, когда вы вводите что-то вроде следующего:

command 2>&1 >/dev/null | grep 'something'

Происходит следующее, по порядку:

  1. создается пайп (fifo); FD1 команды "command" указывает на этот пайп, а FD0 команды "grep" также указывает на него.
  2. FD2 команды "command" указывает на то, на что сейчас указывает FD1 (пайп).
  3. FD1 команды "command" указывает на /dev/null.

Таким образом, весь вывод, который "command" записывает в свой FD2 (stderr), попадает в пайп и считывается "grep" с другой стороны. Весь вывод, который "command" записывает в свой FD1 (stdout), попадает в /dev/null.

Если вместо этого вы выполните следующее:

command >/dev/null 2>&1 | grep 'something'

Происходит следующее:

  1. создается пайп, и FD1 команды "command" и FD0 команды "grep" указывают на него.
  2. FD1 команды "command" указывает на /dev/null.
  3. FD2 команды "command" указывает на то, на что сейчас указывает FD1 (/dev/null).

Таким образом, весь stdout и stderr от "command" попадают в /dev/null. Ничто не поступает в пайп, и, следовательно, "grep" завершится без отображения чего-либо на экране.

Также стоит отметить, что перенаправления (файловые дескрипторы) могут быть только для чтения (<), только для записи (>) или для чтения и записи (<>) .

И напоследок, стоит упомянуть, что то, куда программа записывает данные — в FD1 или FD2, полностью зависит от программиста. Хорошая практика программирования предполагает, что сообщения об ошибках должны выводиться в FD2, а нормальный вывод — в FD1, однако вы часто можете столкнуться с неаккуратным кодом, который смешивает оба типа или игнорирует эту конвенцию.

0

Если вы используете 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 (которые могут включать несколько подсистем и перенаправлений). Это, похоже, нигде не документируется.

0

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь