Как трубу stderr, а не stdout? - PullRequest
886 голосов
/ 26 февраля 2010

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

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

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

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

Ответы [ 12 ]

1061 голосов
/ 26 февраля 2010

Первый перенаправить 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 - а именно, труба.

332 голосов
/ 04 марта 2010

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

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

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

194 голосов
/ 09 февраля 2012

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

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

Для данного случая:

command 2> >(grep 'something') >/dev/null
170 голосов
/ 11 апреля 2013

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

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

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

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

93 голосов
/ 20 августа 2013

Гораздо проще визуализировать вещи, если вы думаете о том, что на самом деле происходит с «перенаправлениями» и «каналами». Перенаправления и каналы в 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, но вы часто найдете неаккуратное программирование, которое смешивает два или иным образом игнорирует соглашение.

33 голосов
/ 19 апреля 2014

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

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

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

9 голосов
/ 14 ноября 2012

Для тех, кто хочет перенаправить 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
5 голосов
/ 07 октября 2014

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

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

Взято из LDP

1 голос
/ 09 августа 2018

Я только что нашел решение отправить 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

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

0 голосов
/ 17 марта 2019

Вы можете использовать rc shell .

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

Это пример того, как вы бы сбросили stdout и направили stderr в grep в rc:

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

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

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

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

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

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