Проводити через фільтр лише STDERR


82

Чи є який-небудь спосіб у bash прокласти STDERR через фільтр перед тим, як об’єднати його зі STDOUT? Тобто я хочу

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

а не

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

Відповіді:


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. Перше збереження stdout як & 3 (& 1 обдурено 3)
  2. Далі надішліть stdout на stderr (& 2 обдурено 1)
  3. Надіслати stderr на & 3 (stdout) (& 3 обдурено 2)
  4. close & 3 (& - обдурено 3)

1
У мене це не працює. Нарешті я змушую це працювати 3>&2 2>&1 1>&3-.
aleung

3
Увага : це припускає, що FD 3 не використовується, і не скасовує обмін дескрипторами файлів 1 і 2, тому ви не можете продовжувати передавати це до іншої команди. Дивіться цю відповідь, щоб отримати додаткові подробиці та навколо роботи. Щоб отримати набагато чистіший синтаксис для {ba, z} sh, див. Цю відповідь .
Том Хейл,

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.
Quinn Comendant

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

чому:; на початку? Я пробував чарівну лінію без, і, схоже, не має значення.
Кошмар

1
Деякі люди використовують $як командний рядок. Тоді вони часто пишуть оболонки приклади , як: $ cat /var/log/syslog | fgrep .... Однак цей рядок не можна копіювати через $. :;виглядає як підказка, але в основному є оболонкою; так що ви можете безпечно вибрати та вставити весь рядок.
solidsnack

23
Гаразд, але ви можете просто опустити:; і рядок також можна скопіювати :) Мені:; не схоже на підказку, це не робить приклад зрозумілішим (відокремлюючи команди від виводу), а бентежить. Хоча я розумію вашу точку зору і не будемо обговорювати синтаксис / домовленості.
Кошмар

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

Остання частина цієї сторінки Розширеного посібника зі створення сценаріїв Bash - це "перенаправлення лише каналу 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 як посилання, оскільки він поєднує документацію, рецепти та думки, не чітко відзначаючи відмінності. Деякі розділи також мають сумнівний вміст, хоча той, на який ви посилаєтесь, здається чудовим.
триплі

3

Погляньте на названі труби:

$ mkfifo err
$ cmd1 2>err |cat - err |cmd2

не cat - errзламає втручання stdout і stderr?
Martin DeMello

@Martin - це залежить. Якщо буфери cmd1, cat або cmd2 виводять, тоді ви можете побачити вихід з послідовності.
моб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.