Как получить стандартный поток ошибок (stderr)?

76

Я использую ffmpeg, чтобы получить метаинформацию аудиоклипа. Но я не могу понять это.

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --prefix=/usr --bindir=/usr/bin 
      --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib
      --mandir=/usr/share/man --arch=i386 --extra-cflags=-O2 
      ...

Я проверил, этот вывод ffmpeg направлен на stderr.

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

Поэтому я думаю, что grep не может прочитать поток ошибок, чтобы поймать совпадающие строки. Как мы можем включить grep для чтения потока ошибок?

Используя ссылку nixCraft , я перенаправил стандартный поток ошибок в стандартный поток вывода, затем сработал grep.

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

Но что, если мы не хотим перенаправлять stderr в stdout?

Andrew-Dufresne
источник
1
Я полагаю, что это grepможет работать только на stdout (хотя я не могу найти канонический источник, подтверждающий это), что означает, что любой поток должен быть сначала преобразован в stdout.
Стефан Ласевский
9
@Stefan: grepможет работать только на стандартный ввод. Это канал, созданный оболочкой, который соединяет stdin grep с stdout другой команды. И оболочка может только подключить стандартный вывод к стандартному вводу.
Жиль "ТАК - перестать быть злым"
Ой, ты прав. Я думаю, что это то, что я действительно хотел сказать, я просто не продумал это. Спасибо @Giles.
Стефан Ласевский
Вы хотите, чтобы он все еще печатал стандартный вывод?
Микель

Ответы:

52

Если вы используете, bashпочему бы не использовать анонимные каналы, по сути, сокращение от того, что phunehehe сказал:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)

Jé Queue
источник
3
+1 Здорово! Bash только, но чище, чем альтернативы.
Микель
1
+1 Я использовал это, чтобы проверить cpвыводcp -r dir* to 2> >(grep -v "svn")
Бетлиста
5
Если вы хотите снова отфильтрованный перенаправленный вывод на stderr, добавьте >&2, например,command 2> >(grep something >&2)
tlo
2
Пожалуйста, объясните, как это работает. 2>перенаправляет stderr в файл, я понял. Это читает из файла >(grep -i Duration). Но файл никогда не сохраняется? Как называется эта техника, чтобы я мог больше узнать об этом?
Марко Авлияш
1
Это «синтаксический сахар» для создания и канала (не файла) и по завершении удаления этого канала. Они фактически анонимны, потому что им не дают имя в файловой системе. Баш называет этот процесс заменой.
Jé Queue
48

Ни одна из обычных оболочек (даже zsh) не позволяет использовать каналы, отличные от stdout-stdin. Но все оболочки Bourne-style поддерживают переназначение дескриптора файла (как в 1>&2). Таким образом, вы можете временно перевести stdout в fd 3 и stderr в stdout, а затем снова поместить fd 3 на stdout. Если stuffвыдает какой-то вывод на stdout и какой-то на stderr, и вы хотите применить filterк выводу ошибки, оставляя стандартный вывод без изменений, вы можете использовать { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1.

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error
Жиль "ТАК - перестань быть злым"
источник
Этот подход работает. К сожалению, в моем случае, если возвращается ненулевое возвращаемое значение, оно теряется - возвращаемое значение равно 0 для меня. Это не всегда может происходить, но это происходит в том случае, когда я сейчас смотрю. Есть ли способ сохранить его?
Фахим Митха
2
@FaheemMitha Не уверен, что ты делаешь, но, возможно, pipestatusэто поможет
Жиль "ТАК - прекрати быть злым"
1
@FaheemMitha, также set -o pipefailможет быть полезным здесь, в зависимости от того, что вы хотите сделать с состоянием ошибки. (Например, если вы set -eвключили с ошибкой при любых ошибках, вы, вероятно, set -o pipefailтакже хотите .)
Wildcard
@Wildcard Да, я set -eвключил, чтобы при любых ошибках.
Фахим Митха
rcОболочка позволяет трубопроводов STDERR. Смотрите мой ответ ниже.
Рольф
20

Это похоже на «трюк временного файла» в phunehehe, но вместо этого использует именованный канал, позволяя вам получить результаты немного ближе к тому, когда они выводятся, что может пригодиться для длительных команд:

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

В этой конструкции stderr будет направлен на трубу с именем «mypipe». Так grepкак был вызван с аргументом файла, он не будет искать STDIN для своего ввода. К сожалению, вам все равно придется очистить эту именованную трубу, как только вы закончите.

Если вы используете Bash 4, существует сокращенный синтаксис для command1 2>&1 | command2, который есть command1 |& command2. Тем не менее, я считаю, что это просто синтаксическое сокращение, вы все еще перенаправляете STDERR в STDOUT.

Стивен Д
источник
Связанный: stackoverflow.com/questions/2871233/…
Stefan Lasiewski
1
Этот |&синтаксис идеально подходит для очистки раздутых трассировок стека Ruby. Наконец-то я могу разобраться с ними без особых хлопот.
РРР
8

Ответы Жиля и Стефана Ласевского хороши, но этот способ проще:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

Я предполагаю, что вы не хотите ffmpeg'sпечатать стандартный вывод.

Как это устроено:

  • трубы в первую очередь
    • Запускаются ffmpeg и grep, причем стандартный вывод ffmpeg идет к стандартному grep
  • перенаправления рядом, слева направо
    • stderr для ffmpeg устанавливается равным его стандартному выводу (в настоящее время канал)
    • Stfout ffmpeg установлен в / dev / null
Mikel
источник
Это описание сбивает меня с толку. Последние две марки заставляют меня думать «перенаправить stderr в stdout», затем «перенаправить stdout (с помощью stderr, сейчас) в / dev / null». Тем не менее, это не то, что это действительно делает. Эти заявления, кажется, полностью изменены.
Стив
1
@ Steve Там нет "с stderr" во второй пули. Вы видели unix.stackexchange.com/questions/37660/order-of-redirections ?
Микель,
Нет, я имею в виду, что это моя интерпретация того, как вы описали это на английском. Ссылка, которую вы предоставили, очень полезна.
Стив
Почему его нужно было перенаправить в / dev / null?
Канвальджит Сингх
7

Ниже приведен скрипт, используемый в этих тестах.

Grep может работать только с stdin, поэтому вы должны преобразовать поток stderr в форму, которую Grep может проанализировать.

Обычно stdout и stderr выводятся на ваш экран:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

Чтобы скрыть стандартный вывод, но все же вывести на печать стандартный вывод, сделайте следующее:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

Но grep не будет работать на stderr! Можно ожидать, что следующая команда подавит строки, содержащие «err», но это не так.

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

Вот решение.

Следующий синтаксис Bash скроет вывод в stdout, но все равно покажет stderr. Сначала мы направляем stdout в / dev / null, затем преобразуем stderr в stdout, потому что каналы Unix будут работать только на stdout. Вы все еще можете получить текст.

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(Обратите внимание , что приведенная выше команда является отличается то ./command >/dev/null 2>&1, что является очень распространенной командой).

Вот скрипт, используемый для тестирования. Это печатает одну строку в stdout и одну строку в stderr:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0
Стефан Ласевский
источник
Если вы переключаете перенаправления, вам не нужны все скобки. Просто делай ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Микель
3

Когда вы перенаправляете вывод одной команды в другую (используя |), вы перенаправляете только стандартный вывод. Так что это должно объяснить, почему

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

не выводит то, что вы хотели (это работает, хотя).

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

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
phunehehe
источник
Благодарю. it does work, thoughВы имеете в виду, что он работает на вашей машине? Во-вторых, как вы указали, используя pipe, мы можем только перенаправить стандартный вывод. Меня интересует какая-то команда или функция bash, которая позволяет мне перенаправлять stderr. (но не уловка временного файла)
Эндрю-Дюфрен
@ Андрей, я имею в виду, команда работает так, как она была разработана для работы. Это просто не работает так, как вы хотите :)
phunehehe
Я не знаю ни одного способа, который мог бы перенаправить вывод ошибки команды на стандартный ввод другой. Было бы интересно, если бы кто-то мог указать на это.
phunehehe
2

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

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

Это работает, сначала создав новый дескриптор файла (3), открытый для вывода, и установив его в стандартный поток ошибок ( 3>&2). Затем мы перенаправляем стандартную ошибку в стандартный вывод ( 2>&1). Наконец стандартный вывод перенаправляется на исходную стандартную ошибку, и новый дескриптор файла закрывается ( 1>&3-).

В твоем случае:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

Тестирование это:

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep "error"
output
error

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep -v "error"
output
Кусалананда
источник
1

Мне нравится использовать rcоболочку в этом случае.

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

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

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

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

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

Как вы могли заметить, синтаксис прост, что делает это моим предпочтительным решением.

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

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

  • 0: стандартный ввод
  • 1: стандартный выход
  • 2: стандартная ошибка
Рольф
источник
0

Вариант на примере подпроцесса bash:

Выведите две строки в stderr и отправьте в файл stderr, grep и отправьте обратно в stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

стандартный вывод:

fff

some.load.errors (например, stderr):

asdf
fff
jmunsch
источник
-1

попробуйте эту команду

  • создать файл со случайным именем
  • отправить вывод в файл
  • отправить содержимое файла в канал
  • Grep

ceva -> просто имя переменной (english = что-то)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;
Родислав Молдаван
источник
Я бы использовал /tmp/$cevaлучше, чем засорять текущий каталог временными файлами - и вы предполагаете, что текущий каталог доступен для записи.
Рольф