Przepuść tylko STDERR przez filtr

82

Czy w bashu jest jakiś sposób na przepuszczenie STDERR przez filtr przed ujednoliceniem go z STDOUT? To znaczy chcę

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

zamiast

STDOUT ────┐
           ├────[ filter ]───> terminal/file/whatever
STDERR ────┘
Martin DeMello
źródło
1
Zobacz także Jak potokować stderr, a nie stdout? .
Andrew Marshall

Odpowiedzi:

64

Oto przykład wzorowany na tym, jak zamienić deskryptory plików w bash . Wynik a.out jest następujący, bez przedrostka „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

Cytując z powyższego linku:

  1. Najpierw zapisz standardowe wyjście jako & 3 (& 1 jest kopiowane do 3)
  2. Następnie wyślij standardowe wyjście na stderr (& 2 jest kopiowane do 1)
  3. Wyślij stderr do & 3 (stdout) (& 3 jest kopiowane do 2)
  4. zamknij & 3 (& - jest podzielone na 3)
Paul Rubel
źródło
1
Na mnie to nie działa. Wreszcie sprawiam, że to działa 3>&2 2>&1 1>&3-.
aleung
3
Uwaga : zakłada się, że FD 3 nie jest używany i nie cofa zamiany deskryptorów plików 1 i 2, więc nie można przejść dalej do innego polecenia. Zobacz tę odpowiedź, aby uzyskać dalsze szczegóły i obejść. Aby uzyskać dużo czystszą składnię {ba, z} sh, zobacz tę odpowiedź .
Tom Hale
25

TL; DR:

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

Przykład:

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

To zadziała zarówno w bash, jak i zsh. Bash jest obecnie prawie wszechobecny, jednak jeśli naprawdę potrzebujesz (naprawdę okrutnego) rozwiązania dla POSIX sh, zobacz tutaj .


Wyjaśnienie

Zdecydowanie najłatwiejszym sposobem na to jest przekierowanie STDERR przez podstawianie procesów :

Podstawianie procesu umożliwia odniesienie się do wejścia lub wyjścia procesu przy użyciu nazwy pliku. Przybiera formę

>(list)

Lista procesów jest uruchamiana asynchronicznie, a jej wejście lub wyjście pojawia się jako nazwa pliku.

Więc to, co otrzymujesz po podstawieniu procesu, to nazwa pliku.

Tak jak możesz:

$ cmd 2> filename

możesz to zrobić

$ cmd 2> >(filter >&2)

>&2Przekierowania filter„s STDOUT z powrotem do pierwotnego stderr.

Tom Hale
źródło
1
Jest pewne zastrzeżenie, które wiąże się z tą odpowiedzią: bash nie czeka na zakończenie procesu zastępowania , podczas gdy żonglerka FD w celu zamiany 1 i 2 tak. To może być ważne. Spaliłem się, robiąc to exec 2> >(while .. read .. echo)w skrypcie działającym jako usługa systemd. journald przechwytuje fd2 usługi i wnioskuje poziom dziennika z przedrostka „<N>”: dołączaj <2> przed wierszami wyjściowymi i otrzymujesz poziom ERROR przypisany do rekordów dziennika. Ale czasami systemd szybciej zabijał całą grupę procesów po wyjściu z głównej, zanim zdążył zarejestrować ostatnią, najważniejszą wiadomość!
kkm
Ładne proste rozwiązanie. Uwaga: uważaj, używając tego jako części zadania cron; podstawianie procesów nie jest dostępne w niektórych powłokach używanych przez crona.
Quinn Comendant
24

Wydaje się, że naiwne stosowanie zastępowania procesów umożliwia filtrowanie stderroddzielnie od stdout:

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

Zauważ, że stderrpojawia się stderri stdoutwłącza stdout, co możemy zobaczyć, opakowując całość w inną podpowłokę i przekierowując do plików oie

( ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 ) ) 1>o 2>e
solidsnack
źródło
dlaczego :; na początku? Próbowałem magicznej linii bez i nie wydaje mi się, żeby to miało znaczenie.
Koshmaar,
1
Niektórzy używają $jako wiersza poleceń. Wtedy często piszą powłoki przykłady takich jak: $ cat /var/log/syslog | fgrep .... Jednak tej linii nie można kopiować i wklejać ze względu na rozszerzenie $. :;wygląda jak zachęta, ale w zasadzie jest to powłoka nie działa; dzięki czemu można bezpiecznie zaznaczyć i wkleić całą linię.
solidsnack
23
Ok, ale możesz po prostu pominąć:; a linia byłaby również kopiowalna i wklejana :) Dla mnie:; nie wygląda jak zachęta, nie czyni przykładu jaśniejszym (oddzielając polecenia od wyjścia), ale wprowadza w błąd. Chociaż rozumiem twój punkt widzenia i nie omawiajmy składni / konwencji.
Koshmaar
15

TL; DR: (bash i zsh)

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

Przykład:

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

Wiele odpowiedzi w sieci StackExchange ma postać:

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

Ma to wbudowane założenie: ten deskryptor pliku 3 nie jest używany do czegoś innego.

Zamiast tego użyj nazwanego deskryptora pliku i {ba,z}shprzydzieli następny dostępny deskryptor pliku> = 10:

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

Zauważ, że nazwane deskryptory plików nie są obsługiwane przez POSIX sh.

Innym problemem związanym z powyższym jest to, że polecenie nie może być przesłane potokiem do dalszych poleceń bez ponownego zamiany STDOUT i STDERR z powrotem na ich oryginalne wartości.

Aby umożliwić shdalsze przesyłanie rur w POSIX (i nadal zakładając, że FD 3 nie jest tego używany), sprawa się komplikuje :

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

Tak więc, biorąc pod uwagę założenie i gnarly składnię tego, prawdopodobnie lepiej będzie, jeśli użyjesz prostszej bash/ zshskładni pokazanej w TL; DR powyżej i wyjaśnionej tutaj .

Tom Hale
źródło
8

Uważam, że podstawienie procesu bash jest łatwiejsze do zapamiętania i używania, ponieważ prawie dosłownie odzwierciedla pierwotny zamiar. Na przykład:

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

używa pierwszej komendy sed jako filtru tylko na stderr, a drugiej komendy sed do modyfikowania połączonych danych wyjściowych.

Zauważ, że spacja po 2> jest obowiązkowa, aby polecenie zostało poprawnie przeanalizowane.

Pecunifex
źródło
5

Ostatnia część tej strony podręcznika Advanced Bash Scripting Guide to „przekierowywanie tylko stderr do potoku”.

# Przekierowywanie tylko stderr do potoku.

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.

# Dzięki, SC

To może być to, czego chcesz. Jeśli nie, jakaś inna część ABSG powinna ci pomóc, to jest doskonałe.

signine
źródło
Wahamy się, czy polecić ABSG jako punkt odniesienia, ponieważ łączy on dokumentację, receptę i opinię bez wyraźnego zaznaczania różnic. Niektóre sekcje mają również wątpliwą zawartość, chociaż ta, do której prowadzą linki, wydaje się w porządku.
tripleee
3

Spójrz na nazwane potoki:

$ mkfifo err
$ cmd1 2>err |cat - err |cmd2
wilhelmtell
źródło
nie cat - errprzerwie przeplatania się stdout i stderr?
Martin DeMello
@Martin - to zależy. Jeśli cmd1, cat lub cmd2 buforują dane wyjściowe, możesz zobaczyć wynik poza kolejnością.
mob