Как выполнить grep для стандартного потока ошибок (stderr)?

Вопрос или проблема

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

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    Версия FFmpeg SVN-r15261, Авторские права (c) 2000-2008 Фабрис Беллар и др.
      конфигурация: --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
  Длительность: 01:15:12.33, начало: 0.000000, битрейт: 64 кбит/с

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

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

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

Ни одна из обычных оболочек (даже zsh) не позволяет использовать каналы с оператором | кроме как от stdout к stdin. Но все оболочки в стиле Bourne поддерживают переназначение дескрипторов файлов (как в 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 стандартный вывод
  echo больше вывода
  echo стандартная ошибка 1>&2
  echo больше ошибок 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
стандартный вывод
больше вывода
стандартная ошибка

ksh, bash и zsh поддерживают каналы на произвольных дескрипторах файлов, но они работают по-разному: одна команда является основной командой, и вы можете перенаправить ее ввод или вывод в другую команду. Подстановка процессов запускает команду в фоновом режиме с подключенным стандартным входом или стандартным выходом к каналу, который вы можете использовать в перенаправлении на основной команде. Здесь вы хотите отфильтровать стандартную ошибку, поэтому перенаправьте ее на выходной процесс подстановки.

$ stuff () {
  echo стандартный вывод
  echo больше вывода
  echo стандартная ошибка 1>&2
  echo больше ошибок 1>&2
}
$ filter () {
  grep a
}
$ stuff 2> >(filter)
стандартный вывод
больше вывода
стандартная ошибка

Обратите внимание, что есть два символа > с пробелом между ними: это 2> для перенаправления стандартного вывода и >(…) для создания подстановки процесса. Пробел необходим, потому что 2>>… будет разобрано как перенаправление добавления.


Чтобы сделать этот подход работать как первая часть ответа, перенаправьте вывод команды filter обратно на stderr:

    ︙
$ stuff 2> >(filter >&2)

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

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

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

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

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

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

Я предполагаю, что вы не хотите, чтобы stdout от ffmpeg выводился.

Как это работает:

  • сначала каналы
    • ffmpeg и grep запускаются, stdout ffmpeg направляется в stdin grep
  • перенаправления далее, слева направо
    • stderr ffmpeg установлен в то, что его stdout (в данный момент канал)
    • stdout ffmpeg установлен в /dev/null

Смотрите ниже сценарий, использованный в этих тестах.

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

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

$ ./stdout-stderr.sh
./stdout-stderr.sh: Печать в stdout
./stdout-stderr.sh: Печать в stderr

Чтобы скрыть stdout, но все же печатать stderr, сделайте это:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Печать в stderr

Но grep не будет работать со stderr! Вы могли бы ожидать, что следующая команда подавит строки, содержащие ‘err’, но это не так.

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Печать в stderr

Вот решение.

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

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Печать в stderr

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

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

#!/bin/sh

# Печать сообщения в stdout
echo "$0: Печать в стандартный вывод"
# Печать сообщения в stderr
echo "$0: Печать в стандартную ошибку" >&2

exit 0

bash может перенаправлять stdout в поток stdin через обычный канал – |
Он также может перенаправлять как stdout, так и stderr в stdin с помощью |&

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

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

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

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

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

Вы можете поменять потоками. Это позволит вам 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

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

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

Это пример того, как вы могли бы отбросить stdout и перенаправить stderr в grep в rc:

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

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

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

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

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

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

  • 0 : Стандартный ввод
  • 1 : Стандартный вывод
  • 2 : Стандартная ошибка

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

вывести две строки в stderr и передать stderr в файл, отфильтровать через tee и вернуть обратно в stdout

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

stdout:

fff

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

asdf
fff

ls FileNotExist  |& grep -v acc

ls FileNotExist 2<&1 | grep -v acc

В этом примере command1 выполняется, и как его вывод, так и любые сообщения об ошибках направляются непосредственно в command2. Это может быть особенно полезно, когда вы хотите отфильтровать или обработать вывод и ошибки команды одинаковым образом.

Оператор |& эквивалентен 2>&1 |, что явно перенаправляет stderr (дескриптор файла 2) в stdout (дескриптор файла 1), а затем направляет комбинированный вывод в другую команду.

Помните, что этот оператор специфичен для Bash shell и может не поддерживаться в других оболочках. Если вы используете другую оболочку, которая не поддерживает |&, вам нужно будет использовать более длинную форму 2>&1 |, чтобы достичь того же результата.

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

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

ceva –> просто имя переменной (английский = что-то)

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

Ответ или решение

Как отфильтровать поток стандартного вывода ошибок (stderr) с помощью grep

Работа с потоками вывода в Unix-системах, таких как Linux, часто вызывает необходимость фильтрации информации. Это особенно актуально для работы с инструментами командной строки, такими как ffmpeg, который выводит важные сообщения об ошибках в стандартный поток ошибок (stderr), а не в стандартный поток вывода (stdout). Если вам нужно использовать grep, чтобы извлечь определенные строки из stderr, это можно сделать несколькими способами. В этом ответе я подробно рассмотрю, как фильтровать stderr и при этом не перенаправлять его в stdout.

Понимание потоков вывода в Unix

Unix-системы используют три стандартных потока:

  • stdin (стандартный ввод) — используется для ввода данных в программу.
  • stdout (стандартный вывод) — используется для вывода данных, которые программа возвращает.
  • stderr (стандартный вывод ошибок) — используется для вывода сообщений об ошибках и предупреждениях.

По умолчанию, команды выводят данные в stdout, в то время как ошибки выводятся в stderr. Это различие позволяет пользователю или программе обрабатывать каждый поток отдельно.

Проблема с использованием grep

Команда grep по умолчанию читает только stdin, который не включает stderr. Поэтому стандартные команды:

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

не сработают, так как вывод ffmpeg о длительности, который вы ищете, находится в stderr.

Методы фильтрации stderr

Если вы хотите отфильтровать stderr, не смешивая его с stdout, вот несколько способов сделать это:

1. Использование файлов для хранения stderr

Одним из простых способов является временное перенаправление stderr в файл, а затем считывание из него:

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

Это работает, но создаёт временные файлы, что не всегда удобно.

2. Использование анонимных каналов

Чтобы избежать создания временных файлов, вы можете использовать анонимные каналы и встроенные механизмы оболочки:

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

В этом примере мы перенаправляем stderr в процесс grep, который фильтрует данные, но без смешивания потоков. Обратите внимание на >(...), что указывает на использование процесса замещения.

3. Изменение потоков с использованием дополнительных дескрипторов

Если вы используете более сложные команды или скрипты, вы можете временно задействовать дополнительные дескрипторы:

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

В этом примере мы создаём временный дескриптор 3, который перенаправляет stdout. После этого stderr перенаправляется в stdout, а stdout — в новый дескриптор. Это позволяет фильтровать только stderr, сохраняя stdout неизменным.

4. Воспользуйтесь встроенными возможностями Bash

С версии Bash 4 вы можете использовать специальный синтаксис:

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

Здесь оператор |& является сокращением для 2>&1 |, объединяя оба потока. Однако это не подходит вам, если нужно сохранить stdout отдельно.

Заключение

Фильтрация стандартного вывода ошибок (stderr) с помощью grep в Unix-системах — это задача, которая может быть выполнена множеством способов в зависимости от ваших потребностей. Вы можете перенаправить stderr в временные файлы, использовать анонимные каналы, временные дескрипторы или воспользоваться встроенными функциями вашей оболочки. При необходимости применяйте соответствующие методы с учетом обстоятельств и сложности вашего рабочего процесса.

Эти подходы обеспечивают гибкость в обработке вывода ошибок без потери данных, что критически важно при работе с важными командами, такими как ffmpeg.

Оцените материал
Добавить комментарий

Капча загружается...