Nur STDERR durch einen Filter leiten

82

Gibt es in bash eine Möglichkeit, STDERR durch einen Filter zu leiten, bevor es mit STDOUT vereinheitlicht wird? Das heißt, ich will

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

eher, als

STDOUT ────┐
           ├────[ filter ]───> terminal/file/whatever
STDERR ────┘
Martin DeMello
quelle
1
Siehe auch Wie man stderr und nicht stdout leitet? .
Andrew Marshall

Antworten:

64

Hier ist ein Beispiel, das dem Austausch von Dateideskriptoren in Bash nachempfunden ist . Die Ausgabe von a.out ist die folgende ohne das Präfix '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

Zitat aus dem obigen Link:

  1. Speichern Sie zuerst stdout als & 3 (& 1 wird in 3 getäuscht)
  2. Als nächstes senden Sie stdout an stderr (& 2 wird in 1 getäuscht)
  3. Sende stderr an & 3 (stdout) (& 3 wird in 2 getäuscht)
  4. close & 3 (& - wird in 3 getäuscht)
Paul Rubel
quelle
1
Es funktioniert nicht bei mir. Endlich mache ich es mit 3>&2 2>&1 1>&3-.
Aleung
3
Achtung : Dies setzt voraus, dass FD 3 nicht verwendet wird, und macht das Austauschen der Dateideskriptoren 1 und 2 nicht rückgängig, sodass Sie dies nicht an einen weiteren Befehl weiterleiten können. In dieser Antwort finden Sie weitere Details und Umgehungsmöglichkeiten. Eine viel sauberere Syntax für {ba, z} sh finden Sie in dieser Antwort .
Tom Hale
24

Eine naive Verwendung der Prozesssubstitution scheint das Filtern von stderrgetrennt von zu ermöglichen stdout:

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

Beachten Sie, dass stderrauf herauskommt stderrund stdoutauf stdout, die wir durch das Einwickeln das Ganze in einem anderen Sub - Shell und Umleitung auf Dateien sehen können ounde

( ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 ) ) 1>o 2>e
Solidsnack
quelle
warum zum :; am Anfang? Ich habe die magische Linie ohne ausprobiert und scheint keinen Unterschied zu machen.
Koshmaar
1
Einige Leute verwenden $als Eingabeaufforderung. Dann schreiben sie oft Shell-Beispiele wie : $ cat /var/log/syslog | fgrep .... Diese Zeile kann jedoch aufgrund der nicht kopiert werden $. :;sieht aus wie eine Eingabeaufforderung, ist aber im Grunde ein Shell-No-Op; So können Sie die gesamte Zeile sicher auswählen und einfügen.
Solidsnack
23
Ok, aber Sie können einfach das weglassen :; und die Zeile wäre auch kopierbar :) Für mich :; sieht nicht wie eine Eingabeaufforderung aus, es hat das Beispiel nicht klarer gemacht (Befehle von der Ausgabe trennen), sondern verwirrend. Obwohl ich Ihren Standpunkt verstehe und nicht auf Syntax / Konventionen eingehen möchte.
Koshmaar
24

TL; DR:

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

Beispiel:

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

Dies funktioniert sowohl in bash als auch in zsh. Bash ist so ziemlich überall in diesen Tagen, aber wenn Sie wirklich eine (wirklich gnarly) Lösung für POSIX benötigen sh, dann hier sehen .


Erläuterung

Der mit Abstand einfachste Weg, dies zu tun, besteht darin, STDERR über die Prozessersetzung umzuleiten :

Durch die Prozessersetzung kann auf die Eingabe oder Ausgabe eines Prozesses unter Verwendung eines Dateinamens verwiesen werden. Es hat die Form von

>(list)

Die Prozessliste wird asynchron ausgeführt und ihre Eingabe oder Ausgabe wird als Dateiname angezeigt.

Was Sie also mit der Prozesssubstitution erhalten, ist ein Dateiname.

Genau wie Sie es tun könnten:

$ cmd 2> filename

du kannst tun

$ cmd 2> >(filter >&2)

Der STDOUT der >&2Weiterleitung filterkehrt zum ursprünglichen STDERR zurück.

Tom Hale
quelle
1
Diese Antwort enthält eine Einschränkung: bash wartet nicht darauf, dass der ersetzte Prozess abgeschlossen ist , während das FD-Jonglieren zum Tauschen von 1 und 2 dies tut. Dies kann wichtig sein. Ich wurde dadurch exec 2> >(while .. read .. echo)in einem Skript verbrannt, das als systemd-Dienst ausgeführt wird. journald erfasst fd2 des Dienstes und leitet die Protokollstufe von einem Präfix '<N>' ab: Vor <2> den Ausgabezeilen voranstellen , und Sie erhalten die Fehlerstufe, die den Journaldatensätzen zugewiesen ist. Aber manchmal war systemd schneller, um die gesamte Prozessgruppe beim Beenden der Hauptgruppe zu beenden, bevor sie ihre letzte, wichtigste Nachricht protokollieren konnte!
km
Schöne einfache Lösung. Vorsichtsmaßnahme: Seien Sie vorsichtig, wenn Sie dies als Teil eines Cron-Jobs verwenden. In einigen von cron verwendeten Schalen ist keine Prozesssubstitution verfügbar.
Quinn Comendant
15

TL; DR: (bash und zsh)

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

Beispiel:

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

Viele Antworten im StackExchange-Netzwerk haben die Form:

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

Dies hat eine eingebaute Annahme: Der Dateideskriptor 3 wird nicht für etwas anderes verwendet.

Verwenden Sie stattdessen einen benannten Dateideskriptor und {ba,z}shweisen Sie den nächsten verfügbaren Dateideskriptor> = 10 zu:

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

Beachten Sie, dass benannte Dateideskriptoren von POSIX nicht unterstützt werden sh.

Das andere Problem mit dem oben Gesagten ist, dass der Befehl nicht an weitere Befehle weitergeleitet werden kann, ohne STDOUT und STDERR wieder auf ihre ursprünglichen Werte zurückzusetzen.

Um eine Weiterleitung in POSIX zu ermöglichen sh(und immer noch davon auszugehen, dass FD 3 nicht verwendet wird), wird es kompliziert :

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

Angesichts der Annahme und der knorrigen Syntax ist es wahrscheinlich besser, die einfachere bash/ zshSyntax zu verwenden, die in der obigen TL; DR gezeigt und hier erläutert wird .

Tom Hale
quelle
8

Ich finde die Verwendung der Bash-Prozess-Substitution leichter zu merken und zu verwenden, da sie die ursprüngliche Absicht fast wörtlich widerspiegelt. Zum Beispiel:

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

Verwendet den ersten sed-Befehl nur als Filter für stderr und den zweiten sed-Befehl, um die verknüpfte Ausgabe zu ändern.

Beachten Sie, dass der Leerraum nach 2> obligatorisch ist, damit der Befehl korrekt analysiert werden kann.

Pecunifex
quelle
5

Der letzte Teil dieser Seite des Advanced Bash Scripting Guide ist "Nur stderr in eine Pipe umleiten".

# Nur stderr zu einem Rohr umleiten.

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.

# Danke, SC

Dies kann sein, was Sie wollen. Wenn nicht, sollte Ihnen ein anderer Teil des ABSG helfen können, es ist ausgezeichnet.

signine
quelle
Wir zögern etwas, das ABSG als Referenz zu empfehlen, da es Dokumentation, Verschreibung und Meinung vermischt, ohne die Unterschiede klar zu kennzeichnen. Einige Abschnitte haben auch zweifelhaften Inhalt, obwohl der, auf den Sie verlinken, in Ordnung zu sein scheint.
Tripleee
3

Schauen Sie sich die genannten Rohre an:

$ mkfifo err
$ cmd1 2>err |cat - err |cmd2
wilhelmtell
quelle
wird nicht cat - errdie Streuung von stdout und stderr brechen?
Martin DeMello
@ Martin - es kommt darauf an. Wenn die Ausgabe von cmd1-, cat- oder cmd2-Puffern erfolgt, wird möglicherweise eine Ausgabe außerhalb der Reihenfolge angezeigt.
Mob