- Вопрос или проблема
- Ответ или решение
- Как отфильтровать поток стандартного вывода ошибок (stderr) с помощью grep
- Понимание потоков вывода в Unix
- Проблема с использованием grep
- Методы фильтрации stderr
- 1. Использование файлов для хранения stderr
- 2. Использование анонимных каналов
- 3. Изменение потоков с использованием дополнительных дескрипторов
- 4. Воспользуйтесь встроенными возможностями Bash
- Заключение
Вопрос или проблема
Я использую 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
.