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

858

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

Я могу, конечно, сделать это за 2 шага:

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

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

  • 0
    Аналогичный вопрос, но с сохранением stdout: unix.stackexchange.com/questions/3514/…
  • 0
    Этот вопрос был для Bash, но стоит упомянуть эту статью для оболочки Bourne / Almquist.
Показать ещё 1 комментарий
Теги:
grep
stdout
pipe
stderr

11 ответов

1020
Лучший ответ

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

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

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

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

  • 31
    я только что наткнулся на / dev / stdout / dev / stderr / dev / stdin на днях, и мне было любопытно, если это хорошие способы сделать то же самое? Я всегда думал, что 2> и 1 были немного запутанными. Так что-то вроде: command 2> /dev/stdout 1> /dev/null | grep 'something'
  • 13
    Вы можете использовать /dev/stdout и др. Или использовать /dev/fd/N Они будут незначительно менее эффективны, если оболочка не будет рассматривать их как особые случаи; Чистая числовая запись не подразумевает доступ к файлам по имени, но использование устройств означает поиск имени файла. Если вы можете измерить это спорно. Мне нравится краткость числовых обозначений - но я использую их так долго (более четверти века; ах!), Что я не обладаю достаточной квалификацией, чтобы судить о ее достоинствах в современном мире.
Показать ещё 25 комментариев
306

Или обменять вывод из 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).

  • 73
    Последней настройкой будет 3>&- чтобы закрыть запасной дескриптор, который вы создали из stdout
  • 1
    Можем ли мы создать файловый дескриптор, в котором есть stderr а другой - в комбинации stderr и stdout ? Другими словами, может ли stderr перейти на два разных файла?
Показать ещё 7 комментариев
193

В 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
  • 0
    @ Тим работает, как и ожидалось.
Показать ещё 2 комментария
161

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

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

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

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

  • 0
    Разве command 2> >(grep -v something) (без 1>&2 ) не одинакова?
  • 11
    Нет, без этого отфильтрованный stderr в конечном итоге будет перенаправлен на стандартный вывод.
Показать ещё 3 комментария
93

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

Когда pipe или "|" оператор присутствует в командной строке, первое, что произойдет, - это то, что 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 (stdout), пробивается к /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 из "command" идут в /dev/null. Ничего не происходит в трубе, и, таким образом, "grep" будет закрываться, не отображая ничего на экране.

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

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

  • 5
    Действительно хороший ответ. Мое единственное предложение - заменить первое использование «fifo» на «fifo (именованная труба)». Я использую Linux некоторое время, но так или иначе никогда не мог понять, что это еще один термин для именованного канала. Это спасло бы меня от поиска, но с другой стороны, я бы не узнал другие вещи, которые я увидел, когда узнал об этом!
  • 3
    @MarkEdington Обратите внимание, что FIFO - это еще один термин для именованного канала в контексте каналов и IPC . В более общем контексте FIFO означает «первым пришел, первым вышел», который описывает вставку и удаление из структуры данных очереди.
Показать ещё 1 комментарий
29

Используете ли вы bash? Если да:

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

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

  • 1
    Нет, |& равно 2>&1 что объединяет stdout и stderr. Вопрос явно задан для вывода без stdout.
  • 3
    «Если используется« | & », стандартная ошибка команды 1 связана со стандартным входом команды 2 через канал; это сокращение от 2> & 1 | ”. Взято дословно из четвертого абзаца по вашей ссылке.
Показать ещё 4 комментария
9

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

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

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

Взято из ЛДП

1

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

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

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

  • 0
    Что это должно делать?
  • 0
    Передает выходные данные во временный файл, открывает его с помощью vim, а затем удаляет его
-4

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

command > /dev/null 2>&1 | grep 'something'
  • 0
    Не работает Он просто отправляет stderr в терминал. Игнорирует трубу.
  • 0
    @TrippKinetics отредактировано

Ещё вопросы

Сообщество Overcoder
Наверх
Меню