Пропускайте только STDERR через фильтр

82

Есть ли способ в bash пропустить STDERR через фильтр перед его объединением с STDOUT? То есть я хочу

STDOUT ────────────────┐
                       ├─────> terminal/file/whatever
STDERR ── [ filter ] ──┘

скорее, чем

STDOUT ────┐
           ├────[ filter ]───> terminal/file/whatever
STDERR ────┘
Мартин ДеМелло
источник
1
См. Также Как передать stderr, а не stdout? .
Эндрю Маршалл

Ответы:

64

Вот пример, смоделированный после того, как поменять местами дескрипторы файлов в bash . Вывод a.out следующий без префикса «STDXXX:».

STDERR: stderr output
STDOUT: more regular

./a.out 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'
more regular
stdErr output

Цитата из приведенной выше ссылки:

  1. Сначала сохраните стандартный вывод как & 3 (& 1 дублируется в 3)
  2. Затем отправьте stdout в stderr (& 2 дублируется в 1)
  3. Отправить stderr в & 3 (stdout) (& 3 дублируется в 2)
  4. закрыть & 3 (& - дублируется на 3)
Пол Рубель
источник
1
У меня это не работает. Наконец я заставил его работать 3>&2 2>&1 1>&3-.
aleung
3
Внимание : предполагается, что FD 3 не используется, и не отменяет замену файловых дескрипторов 1 и 2, поэтому вы не можете передать это еще одной команде. См. Этот ответ для получения дополнительных сведений и способов решения проблемы. Более чистый синтаксис для {ba, z} sh см. В этом ответе .
Tom Hale
25

TL; DR:

$ cmd 2> >(stderr-filter >&2)

Пример:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

Это будет работать как в bash, так и в zsh. В наши дни Bash в значительной степени повсеместен, однако, если вам действительно нужно (действительно грубое) решение для POSIX sh, посмотрите здесь .


Объяснение

Безусловно, самый простой способ сделать это - перенаправить STDERR с помощью замены процесса :

Подстановка процесса позволяет ссылаться на вход или выход процесса с использованием имени файла. Он принимает форму

>(list)

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

Таким образом, при подстановке процесса вы получаете имя файла.

Так же, как вы могли бы сделать:

$ cmd 2> filename

ты можешь сделать

$ cmd 2> >(filter >&2)

>&2Редирект - х filter«s STDOUT обратно к первоначальному STDERR.

Том Хейл
источник
1
Существует предостережение, связанное с этим ответом: bash не ожидает завершения замещенного процесса , в то время как FD жонглирует для обмена местами 1 и 2. Это может быть важно. Я был обожжен этим выполнением exec 2> >(while .. read .. echo)сценария, работающего как служба systemd. journald захватывает fd2 службы и определяет уровень журнала по префиксу «<N>»: добавление <2> к строкам вывода, и вы получаете уровень ОШИБКИ, назначенный записям журнала. Но иногда systemd был быстрее, чтобы убить всю группу процессов при выходе из основной, прежде чем она успела записать свое последнее, самое важное сообщение!
kkm
Хорошее простое решение. Предупреждение: будьте осторожны, используя это как часть работы cron; замена процесса недоступна в некоторых оболочках, используемых cron.
Куинн Комендант
24

Наивное использование подстановки процесса, кажется, позволяет фильтровать stderrотдельно от stdout:

:; ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 )
out
e:err

Обратите внимание , что stderrвыходит на stderrи stdoutна stdout, которые мы можем видеть, завернув все это в другой субоболочке и перенаправление файлы oиe

( ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 ) ) 1>o 2>e
твердый закуска
источник
Почему :; с начала? Я пробовал волшебную линию без нее, и, похоже, это не имеет значения.
Koshmaar
1
Некоторые люди используют его $как командную строку. Тогда они часто пишут оболочки примеры , как: $ cat /var/log/syslog | fgrep .... Однако эту строку нельзя скопировать из-за расширения $. :;выглядит как приглашение, но, по сути, не работает с оболочкой; так что вы можете безопасно выбрать и вставить всю строку.
solidsnack
23
Хорошо, но вы можете просто опустить:; и строка также была бы копируемой :) Мне:; не выглядит как приглашение, это не делает пример более ясным (отделяет команды от вывода), но сбивает с толку. Хотя я понимаю вашу точку зрения и не будем обсуждать синтаксис / соглашения.
Koshmaar
15

TL; DR: (bash и zsh)

$ cmd 2> >(stderr-filter >&2)

Пример:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

Многие ответы в сети StackExchange имеют форму:

cat /non-existant 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'

У этого есть встроенное предположение: этот файловый дескриптор 3 не используется для чего-то еще.

Вместо этого используйте именованный файловый дескриптор и {ba,z}shвыделите следующий доступный файловый дескриптор> = 10:

cat /non-existant {tmp}>&1 1>&2 2>&$tmp {tmp}>&- | sed 's/e/E/g'

Обратите внимание, что именованные файловые дескрипторы не поддерживаются POSIX. sh .

Другая проблема, связанная с вышеизложенным, заключается в том, что команду нельзя передать по конвейеру другим командам без повторной замены STDOUT и STDERR на их исходные значения.

Чтобы разрешить последующий конвейер в POSIX sh(и все еще предполагая, что FD 3 не используется), это усложняется :

(cmd 2>&1 >&3 3>&- | stderr-filter >&2 3>&-) 3>&1

Итак, учитывая предположение и грубый синтаксис этого, вам, вероятно, будет лучше использовать более простой bash/ zshсинтаксис, показанный в TL; DR выше и объясненный здесь .

Том Хейл
источник
8

Я считаю, что использование замены процесса bash легче запомнить и использовать, поскольку оно почти дословно отражает исходное намерение. Например:

$ cat ./p
echo stdout
echo stderr >&2
$ ./p 2> >(sed -e 's/s/S/') | sed 's/t/T/'
sTdout
STderr

использует первую команду sed в качестве фильтра только для stderr, а вторую команду sed для изменения объединенного вывода.

Обратите внимание, что пробел после 2> является обязательным для правильного анализа команды.

Пекунифекс
источник
5

Последняя часть этой страницы Advanced Bash Scripting Guide - «перенаправление только stderr на канал».

# Перенаправление в канал только stderr.

exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

# Спасибо, SC

Это может быть то, что вы хотите. Если нет, то какая-то другая часть ABSG сможет вам помочь, это отлично.

Signine
источник
Мы несколько не решаемся рекомендовать ABSG в качестве справочного материала, поскольку он сочетает в себе документацию, предписания и мнения без четкого обозначения различий. Некоторые разделы также имеют сомнительное содержание, хотя тот, на который вы ссылаетесь, кажется нормальным.
Tripleee 07
3

Взгляните на именованные каналы:

$ mkfifo err
$ cmd1 2>err |cat - err |cmd2
Wilhelmtell
источник
не cat - errнарушит чередование stdout и stderr?
Мартин ДеМелло 01
@Martin - это зависит от обстоятельств. Если выводятся буферы cmd1, cat или cmd2, то вы можете увидеть вывод вне последовательности.
моб