Как трубу 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'
Вот что происходит по порядку:
- труба (fifo) создана. "команда FD1" указывает на эту трубу. "grep FD0" также указывает на эту трубу
- "команда FD2" указывает на то, где в настоящий момент указывает "команда FD1" (канал)
- "команда FD1" указывает на /dev/null
Таким образом, весь вывод, который "команда" записывает в свой FD 2 (stderr), попадает в канал и читается "grep" с другой стороны. Все выходные данные, которые "команда" записывает в свой FD 1 (стандартный вывод), попадают в /dev/null.
Если вместо этого вы запускаете следующее:
command >/dev/null 2>&1 | grep 'something'
Вот что происходит:
- создается канал, и на него указывают "команда FD 1" и "grep FD 0"
- "команда FD 1" указывает на /dev/null
- "команда FD 2" указывает на то, куда в данный момент указывает FD 1 (/dev/null)
Итак, все stdout и stderr из "команды" идут в /dev/null. Ничто не идет к трубе, и, таким образом, "grep" закроется, не показывая ничего на экране.
Также обратите внимание, что перенаправления (файловые дескрипторы) могут быть только для чтения (<), только для записи (>) или для чтения-записи (<>).
Последнее замечание Записывает ли программа что-то в FD1 или FD2, полностью зависит от программиста. Хорошая практика программирования гласит, что сообщения об ошибках должны отправляться в FD 2, а нормальный вывод - в FD 1, но вы часто будете обнаруживать неаккуратное программирование, которое смешивает два или иным образом игнорирует соглашение.
Вы используете Bash? Если так:
command >/dev/null |& grep "something"
Для тех, кто хочет перенаправить 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'