Як схопити стандартний потік помилок (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 , я перенаправляв стандартний потік помилок на стандартний вихідний потік, після чого працював греп .

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

Але що робити, якщо ми не хочемо перенаправляти stderr на stdout?


1
Я вважаю, що це grepможе працювати лише на stdout (Хоча я не можу знайти канонічне джерело, яке б створити резервну копію), це означає, що будь-який потік потрібно перетворити спочатку на stdout.
Стефан Ласєвський

9
@Stefan: grepможе працювати лише на stdin. Це труба, створена оболонкою, яка з'єднує stdin grep з версією іншої команди. І оболонка може з'єднувати лише stdout до stdin.
Жиль

Упс, ти маєш рацію. Я думаю, що це я дійсно мав намір сказати, я просто не продумав це. Дякую @Giles.
Стефан Ласєвський

Ви хочете, щоб він все ще друкував stdout?
Мікель

Відповіді:


52

Якщо ви використовуєте, bashчому б не використовувати анонімні труби, по суті це скорочення для того, що сказав phunehehe:

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


3
+1 Приємно! Баш тільки, але спосіб чистіший за альтернативи.
Мікель

1
+1 Я використав це для перевірки cpрезультатівcp -r dir* to 2> >(grep -v "svn")
Betlista

5
Якщо ви хочете знову відфільтрований перенаправлений вихід на stderr, додайте a >&2, наприкладcommand 2> >(grep something >&2)
tlo

2
Поясніть, будь ласка, як це працює. 2>перенаправляє stderr до файлу, я розумію. Це читається з файлу >(grep -i Duration). Але файл ніколи не зберігається? Як називається ця методика, щоб я міг прочитати про неї більше?
Марко Авліяш

1
Це "синтаксичний цукор", щоб створити і трубу (не файл), і коли завершити видалення цієї труби. Вони фактично анонімні, оскільки їм не надається ім’я у файловій системі. Bash називає цей процес заміщення.
Черга Jé

48

Жодна із звичайних оболонок (навіть zsh) не дозволяє проводити труби, відмінні від stdout до stdin. Але всі оболонки в стилі Борна підтримують перепризначення дескриптора файлу (як у 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. Це може бути не завжди, але це трапляється у випадку, коли я зараз дивлюся. Чи є якийсь спосіб зберегти це?
Faheem Mitha

2
@FaheemMitha Не впевнений, чим ти займаєшся, але, можливо pipestatus, допоможе
Жиль,

1
@FaheemMitha, також set -o pipefailможе бути корисний, в залежності від того, що ви хочете зробити зі статусом помилки. (Наприклад, якщо ви set -eввімкнули помилку в будь-яких помилках, ви, мабуть, хочете set -o pipefailтакож.)
Wildcard

@Wildcard Так, я не set -eввімкнув помилки.
Faheem Mitha

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.



1
Цей |&синтаксис ідеально підходить для очищення роздутих слідів стеку Ruby stderr. Нарешті я можу поздоровитись без зайвих клопотів.
пгр

8

Відповіді Джилла та Стефана Ласєвського є хорошими, але цей спосіб простіший:

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

Я припускаю, що ви не хочете ffmpeg'sдрукувати stdout.

Як це працює:

  • труби спочатку
    • ffmpeg і grep запускаються, при цьому stdout ffmpeg переходить до stdin grep
  • перенаправлення далі, зліва направо
    • ffmpeg's stderr встановлюється на будь-який його stdout (на даний момент труба)
    • У викладі ffmpeg встановлено значення / dev / null

Цей опис мене бентежить. Останні дві кулі змушують мене думати "перенаправити stderr на stdout", потім "перенаправити stdout (з stderr, зараз) на / dev / null". Однак це насправді не так. Ці твердження, здається, зворотні.
Стів

1
@Steve У другій кулі немає "з stderr". Ви бачили unix.stackexchange.com/questions/37660/order-of-redirections ?
Мікель

Ні, я маю на увазі, це моя інтерпретація того, як ви описали це англійською. Надане вами посилання дуже корисне.
Стів

Чому його потрібно було переспрямувати на / dev / null?
Kanwaljeet Singh

7

Нижче див. Сценарій, використаний у цих тестах.

Grep може працювати лише на stdin, тому ви повинні конвертувати потік stderr у форму, яку Греп може проаналізувати.

Зазвичай, stdout і stderr друкуються на екрані:

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

Щоб приховати stdout, але все-таки надрукувати 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

не виводить те, що ви хотіли (хоча це все-таки працює).

Якщо ви не хочете перенаправляти вихід помилок на стандартний вихід, ви можете перенаправити вихід помилки на файл, а потім видалити його пізніше

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error

Дякую. it does work, though, ви маєте на увазі, що він працює на вашій машині? По-друге, як ви вказали, використовуючи трубу, ми можемо лише перенаправляти виводку. Мене цікавить якась функція команди або bash, яка дозволить мені перенаправляти stderr. (але не фокус із тимчасовим файлом)
Andrew-Dufresne

@Andrew Я маю на увазі, команда працює так, як вона була розроблена для роботи. Це просто не працює так, як ви хочете :)
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і перекладете stderrна греп 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 і tee stderr до файлу, притискайте трійник і трубу назад на stdout

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

stdout:

fff

some.load.errors (наприклад, stderr):

asdf
fff

-1

спробуйте цю команду

  • створити файл із випадковим іменем
  • відправити вихід у файл
  • відправити вміст файлу на трубу
  • греп

ceva -> просто ім'я змінної (англійська = щось)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;

Я б використав /tmp/$ceva, краще, ніж засипати поточний каталог тимчасовими файлами - і ви припускаєте, що поточний каталог можна записати.
Рольф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.