Как трубу stderr, а не stdout?

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

Конечно, я могу сделать это в 2 этапа:

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

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

11 ответов

Решение

Сначала перенаправьте stderr в stdout - канал; затем перенаправить стандартный вывод на /dev/null (без изменения куда идет stderr):

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

Подробнее о перенаправлении ввода / вывода во всех его разновидностях см. Главу о перенаправлениях в справочном руководстве Bash.

Обратите внимание, что последовательность перенаправлений ввода / вывода интерпретируется слева направо, но каналы устанавливаются до того, как перенаправления ввода / вывода интерпретируются. Файловые дескрипторы, такие как 1 и 2, являются ссылками на описания открытых файлов. Операция 2>&1 заставляет файловый дескриптор 2, он же stderr, ссылаться на то же самое описание открытого файла, что и файловый дескриптор 1, он же stdout, в данный момент ссылается dup2() а также open()). Операция >/dev/null затем изменяет дескриптор файла 1 так, чтобы он ссылался на описание открытого файла для /dev/null, но это не меняет того факта, что дескриптор файла 2 ссылается на описание открытого файла, на которое первоначально указывал дескриптор файла 1, а именно на канал.

Или обменять вывод из stderr и stdout на использование:-

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

Это создает новый файловый дескриптор (3) и назначает его в то же место, что и 1 (stdout), затем назначает fd 1 (stdout) в то же место, что и fd 2 (stderr), и, наконец, назначает fd 2 (stderr) тому же место как fd 3 (стандартный вывод). Stderr теперь доступен как stdout, а старый stdout сохранен в stderr. Это может быть излишним, но, надеюсь, даст больше подробностей о дескрипторах файлов bash (для каждого процесса доступно 9).

В Bash вы также можете перенаправить на подоболочку, используя подстановку процессов:

command > >(stdlog pipe)  2> >(stderr pipe)

Для рассматриваемого случая:

command 2> >(grep 'something') >/dev/null

Объединяя лучшие из этих ответов, если вы делаете:

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

... тогда все stdout сохраняются как stdout, а все stderr сохраняются как stderr, но вы не увидите никаких строк в stderr, содержащих строку "что-то".

Это имеет уникальное преимущество, заключающееся в том, что они не меняют и не отбрасывают 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'

Вот что происходит по порядку:

  1. труба (fifo) создана. "команда FD1" указывает на эту трубу. "grep FD0" также указывает на эту трубу
  2. "команда FD2" указывает на то, где в настоящий момент указывает "команда FD1" (канал)
  3. "команда FD1" указывает на /dev/null

Таким образом, весь вывод, который "команда" записывает в свой FD 2 (stderr), попадает в канал и читается "grep" с другой стороны. Все выходные данные, которые "команда" записывает в свой FD 1 (стандартный вывод), попадают в /dev/null.

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

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

Вот что происходит:

  1. создается канал, и на него указывают "команда FD 1" и "grep FD 0"
  2. "команда FD 1" указывает на /dev/null
  3. "команда FD 2" указывает на то, куда в данный момент указывает FD 1 (/dev/null)

Итак, все stdout и stderr из "команды" идут в /dev/null. Ничто не идет к трубе, и, таким образом, "grep" закроется, не показывая ничего на экране.

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

Последнее замечание Записывает ли программа что-то в FD1 или FD2, полностью зависит от программиста. Хорошая практика программирования гласит, что сообщения об ошибках должны отправляться в FD 2, а нормальный вывод - в FD 1, но вы часто будете обнаруживать неаккуратное программирование, которое смешивает два или иным образом игнорирует соглашение.

Вы используете Bash? Если так:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html

Для тех, кто хочет перенаправить stdout и stderr навсегда в файлы, используйте grep для stderr, но оставляйте stdout для записи сообщений в tty:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3

Это перенаправит команду command1 stderr на команду command2 stdin, оставив команду command1 stdout как есть.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Взято из ЛДП

Я просто придумал решение для отправки stdout одной команде и stderr к другому, используя именованные каналы.

Вот оно.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

Вероятно, это хорошая идея удалить именованные каналы позже.

Вы можете использовать оболочку rc.

Сначала установите пакет (он меньше 1 МБ).

Это пример того, как вы откажетесь от стандартного вывода и передадите стандартную ошибку в grep вrc:

find /proc/ >[1] /dev/null |[2] grep task

Вы можете сделать это, не выходя из Bash:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

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

Стандартные файловые дескрипторы нумеруются следующим образом:

  • 0: Стандартный ввод
  • 1: Стандартный вывод
  • 2: Стандартная ошибка

Я склонен делать такие вещи, как

тест композитора &>> /tmp/bob && vim /tmp/bob && rm /tmp/bob

Я пытаюсь следовать, найти это работать, а также,

command > /dev/null 2>&1 | grep 'something'
Другие вопросы по тегам