Как я могу трубу stderr, а не stdout?

982

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

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

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

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

Питер Мортенсен
источник
Аналогичный вопрос, но с сохранением stdout: unix.stackexchange.com/questions/3514/…
joeytwiddle
Этот вопрос был для Bash, но стоит упомянуть эту статью для оболочки Bourne / Almquist.
Стивен Недзельски
10
Я ожидал что-то вроде этого command 2| othercommand. Bash настолько совершенен, что разработка закончилась в 1982 году, поэтому, боюсь, мы никогда этого не увидим.
Рольф

Ответы:

1190

Сначала перенаправьте stderr в stdout - канал; затем перенаправить 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, а именно на канал.

Джонатан Леффлер
источник
44
я только что наткнулся на / dev / stdout / dev / stderr / dev / stdin на днях, и мне было любопытно, если это хорошие способы сделать то же самое? Я всегда думал, что 2> и 1 были немного запутанными. Так что-то вроде: command 2> /dev/stdout 1> /dev/null | grep 'something'
Майк Лайонс
17
Вы можете использовать /dev/stdoutet al, или использовать /dev/fd/N. Они будут незначительно менее эффективны, если оболочка не будет рассматривать их как особые случаи; Чистая числовая запись не предполагает доступа к файлам по имени, но использование устройств означает поиск имени файла. Если вы можете измерить это спорно. Мне нравится краткость числовых обозначений - но я использую их так долго (более четверти века; ах!), Что я не обладаю достаточной квалификацией, чтобы судить о ее достоинствах в современном мире.
Джонатан Леффлер
23
@Jonathan Leffler: у меня небольшая проблема с вашим простым текстовым объяснением «Перенаправить stderr в stdout, а затем в stdout в / dev / null» - поскольку нужно читать цепочки перенаправления справа налево (не слева направо), мы также следует адаптировать наше простое текстовое объяснение к этому: «Перенаправить стандартный вывод в / dev / null, а затем в stderr туда, где раньше был стандартный вывод» .
Курт Пфайфл
116
@KurtPfeifle: с другой стороны! Надо читать цепочки перенаправления слева направо, поскольку именно так их обрабатывает оболочка. Первая операция - это 2>&1, что означает «подключить stderr к дескриптору файла, к которому в данный момент идет stdout ». Вторая операция - «изменить стандартный вывод так, чтобы он пошел /dev/null», оставив стандартный вывод на исходный стандартный канал - канал. Оболочка сначала разбивает объекты по символу канала, поэтому перенаправление канала происходит перед 2>&1или >/dev/nullперенаправлениями, но это все; другие операции слева направо. (Справа налево не будет работать.)
Джонатан Леффлер
14
Что меня действительно удивляет в этом, так это то, что он работает и в Windows (после переименования /dev/nullв эквивалент Windows nul).
Майкл Берр
364

Или для замены вывода из стандартной ошибки и стандартного вывода используйте:

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

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

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

Kramish
источник
100
Последним изменением было бы 3>&-закрыть запасной дескриптор, который вы создали из stdout
Джонатан Леффлер
1
Можем ли мы создать дескриптор файла, который имеет stderrи другой, который имеет комбинацию stderrи stdout? Другими словами можно stderrперейти к двум разным файлам одновременно?
Стюарт
Следующее все еще печатает ошибки в стандартный вывод. Что мне не хватает? ls -l not_a_file 3> & 1 1> & 2 2> & 3> errors.txt
user48956
1
@ JonasDahlbæk: настройка - это прежде всего вопрос аккуратности. В поистине загадочных ситуациях это может иметь значение для процесса, обнаруживающего и не обнаруживающего EOF, но это требует очень специфических обстоятельств.
Джонатан Леффлер
1
Предостережение : это предполагает, что FD 3 еще не используется, не закрывает его и не отменяет обмен файловыми дескрипторами 1 и 2, поэтому вы не можете продолжать передавать это еще одной команде. Смотрите этот ответ для более подробной информации и обходного пути. Более понятный синтаксис для {ba, z} sh смотрите в этом ответе .
Том Хейл,
218

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

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

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

command 2> >(grep 'something') >/dev/null
Рич Джонсон
источник
1
Очень хорошо работает для вывода на экран. Есть ли у вас какие-либо идеи, почему несвязанный контент появляется снова, если я перенаправляю вывод grep в файл? После того, как command 2> >(grep 'something' > grep.log)grep.log содержит тот же вывод, что и ungrepped.log отcommand 2> ungrepped.log
Тим
9
Использование 2> >(stderr pipe >&2). В противном случае вывод «stderr pipe» будет проходить через «stdlog pipe».
ceving
да !, 2> >(...)работает, я пытался, 2>&1 > >(...)но не получилось
datdinhquoc
Вот небольшой пример, который может помочь мне в следующий раз, когда я посмотрю, как это сделать. Подумайте о следующем ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) В этом случае я хотел также увидеть, что выдает ошибки на моей консоли. Но STDOUT собирался в выходной файл. Таким образом, внутри вложенной оболочки вам нужно перенаправить этот STDOUT обратно в STDERR внутри скобок. Пока это работает, вывод STDOUT из teeкоманды завершается в конце out-content.txtфайла. Это кажется мне неуместным.
будет
@datdinhquoc Я сделал это как-то как2>&1 1> >(dest pipe)
Алиреза Мохамади
195

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

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

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

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

Pinko
источник
Разве command 2> >(grep -v something)(без 1>&2) то же самое?
Франческ Росас
11
Нет, без этого отфильтрованный stderr в конечном итоге будет перенаправлен на стандартный вывод.
Пинко
1
Это то, что мне было нужно - tar выводит «файл, который мы изменили, как мы его читаем», для каталога всегда, поэтому просто хочу отфильтровать эту строку, но посмотрим, не возникнут ли другие ошибки. Так tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)должно работать.
прорвался
неправильно говорить «начиная со строки». В представленном синтаксисе grep нет ничего, что позволило бы исключить только строки, начинающиеся с данной строки. Строки будут исключены, если они содержат данную строку где-либо в них.
Майк Накис
@MikeNakis спасибо - исправлено! (Это было из моего первоначального черновика ответа, в котором это имело смысл ...)
Пинко
102

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

Майкл Мартинес
источник
6
Действительно хороший ответ. Мое единственное предложение - заменить первое использование «fifo» на «fifo (именованный канал)». Я уже давно пользуюсь Linux, но так или иначе не смог понять, что это еще один термин для именованного канала. Это спасло бы меня от поиска, но опять же я бы не узнал другие вещи, которые я увидел, когда узнал об этом!
Марк Эдингтон
3
@MarkEdington Обратите внимание, что FIFO - это еще один термин для именованного канала в контексте каналов и IPC . В более общем контексте FIFO означает «первым пришел, первым вышел», который описывает вставку и удаление из структуры данных очереди.
Loomchild
5
@Loomchild Конечно. Суть моего комментария заключалась в том, что даже будучи опытным разработчиком, я никогда не видел, чтобы FIFO использовался как синоним именованного канала. Другими словами, я не знал этого: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - разъяснение того, что в ответе сэкономило бы мне время.
Марк Эдингтон
39

Если вы используете Bash, то используйте:

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

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

Кен Шарп
источник
4
Нет, |&равно 2>&1которому сочетает в себе stdout и stderr. Вопрос явно задан для вывода без stdout.
Profpatsch
3
«Если используется« | & », стандартная ошибка команды 1 связана со стандартным входом команды 2 через канал; это сокращение для 2> & 1 | ” Взято дословно из четвертого абзаца по вашей ссылке.
Profpatsch
9
@Profpatsch: ответ Кена верный, посмотрите, что он перенаправляет stdout на ноль, прежде чем объединить stdout и stderr, так что вы получите в конвейере только stderr, потому что stdout ранее был сброшен в / dev / null.
Лучано
3
Но я все же обнаружил, что ваш ответ неправильный, >/dev/null |&расширите до >/dev/null 2>&1 | и означает, что inode stdout пуст по каналу, потому что никто (# 1 # 2, связанный с / dev / null inode) не связан с inode stdout (например ls -R /tmp/* >/dev/null 2>&1 | grep i, даст пустой, но ls -R /tmp/* 2>&1 >/dev/null | grep iпозволит # 2 который привязан к stdout inode будет труба).
Фрукт
3
Кен Шарп, я проверял, и не ( echo out; echo err >&2 ) >/dev/null |& grep "."дает никакого вывода (где мы хотим "ошибаться"). man bashговорит, что если | & используется… это сокращение от 2> & 1 |. Это неявное перенаправление стандартной ошибки на стандартный вывод выполняется после любых перенаправлений, указанных в команде. Итак, сначала мы перенаправляем FD1 команды на ноль, затем перенаправляем FD2 команды туда, куда указал FD1, т.е. NULL, поэтому grep FD0 не получает ввода. См. Stackoverflow.com/a/18342079/69663 для более подробного объяснения.
unhammer 22.09.16
11

Для тех, кто хочет перенаправить 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
JBD
источник
6

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

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

Взято из ЛДП

Дельфин
источник
2

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

Вы можете использовать оболочку 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: стандартная ошибка
Рольф
источник
-3

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

command > /dev/null 2>&1 | grep 'something'
lasteye
источник
Не работает Он просто отправляет stderr в терминал. Игнорирует трубу.
Tripp Kinetics