Wie grep Standard Error Stream (stderr)?

76

Ich verwende ffmpeg, um die Metainformationen eines Audioclips abzurufen. Aber ich kann es nicht fassen.

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --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 
      ...

Ich habe überprüft, dass diese ffmpeg-Ausgabe an stderr gerichtet ist.

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

Daher denke ich, dass grep den Fehlerstrom nicht lesen kann, um übereinstimmende Zeilen abzufangen. Wie kann grep das Lesen von Fehlerströmen ermöglichen?

Unter Verwendung von nixCraft link habe ich den Standardfehlerstrom auf den Standardausgabestrom umgeleitet, dann hat grep funktioniert.

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

Was aber, wenn wir stderr nicht auf stdout umleiten wollen?

Andrew-Dufresne
quelle
1
Ich glaube, das grepkann nur mit stdout funktionieren (obwohl ich die kanonische Quelle nicht finden kann, um das zu sichern), was bedeutet, dass jeder Stream zuerst in stdout konvertiert werden muss.
Stefan Lasiewski
9
@Stefan: grepKann nur mit stdin arbeiten. Es ist die Pipe, die von der Shell erstellt wurde und die die Standardeingabe von grep mit der Standardeingabe des anderen Befehls verbindet. Und die Shell kann nur einen stdout mit einem stdin verbinden.
Gilles
Hoppla, du hast recht. Ich denke, das wollte ich wirklich sagen, ich habe es einfach nicht durchdacht. Danke @Giles.
Stefan Lasiewski
Möchten Sie, dass es immer noch stdout druckt?
Mikel

Antworten:

52

Wenn Sie bashanonyme Pipes verwenden, verwenden Sie im Wesentlichen die Abkürzung für das, was phunehehe sagte:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)

Jé Queue
quelle
3
+1 Schön! Bash nur, aber viel sauberer als die Alternativen.
Mikel
1
+1 Ich habe das benutzt, um die cpAusgabe zu überprüfencp -r dir* to 2> >(grep -v "svn")
Betlista
5
Wenn Sie das gefilterte umgeleitet Ausgabe auf stderr wieder wollen, fügen Sie ein >&2, zum Beispielcommand 2> >(grep something >&2)
tlo
2
Bitte erläutern Sie, wie das funktioniert. 2>leitet stderr in die Datei um, das verstehe ich. Dies liest aus der Datei >(grep -i Duration). Aber die Datei wird nie gespeichert? Wie heißt diese Technik, damit ich mehr darüber lesen kann?
Marko Avlijaš
1
Es ist "syntaktischer Zucker", eine Pipe (keine Datei) zu erstellen und diese Pipe zu entfernen, wenn sie fertig ist. Sie sind praktisch anonym, weil sie im Dateisystem keinen Namen haben. Bash nennt diesen Vorgang Substitution.
Jé Queue
48

Keine der üblichen Schalen (sogar zsh) erlaubt andere Pfeifen als stdout bis stdin. Alle Shells im Bourne-Stil unterstützen jedoch die Neuzuweisung von Dateideskriptoren (wie in 1>&2). Sie können also stdout vorübergehend auf fd 3 und stderr auf stdout umleiten und später fd 3 wieder auf stdout setzen. Wenn stuffeine Ausgabe auf stdout und eine Ausgabe auf stderr erzeugt wird und Sie filterauf die Fehlerausgabe anwenden möchten, ohne die Standardausgabe zu verändern, können Sie verwenden { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1.

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error
Gilles
quelle
Dieser Ansatz funktioniert. Leider geht in meinem Fall ein Rückgabewert ungleich Null verloren - der zurückgegebene Wert ist für mich 0. Das passiert vielleicht nicht immer, aber es passiert in dem Fall, den ich gerade betrachte. Gibt es eine Möglichkeit, es zu retten?
Faheem Mitha
2
@FaheemMitha Nicht sicher, was Sie tun, aber vielleicht pipestatuswürde helfen
Gilles
1
@FaheemMitha, set -o pipefailkann auch hier hilfreich sein, je nachdem, was Sie mit dem Fehlerstatus machen möchten. (Wenn Sie beispielsweise aktiviert haben, set -eum Fehler zu beheben, möchten Sie wahrscheinlich set -o pipefailauch.)
Wildcard
@Wildcard Ja, ich habe set -eeingeschaltet, um bei Fehlern zu scheitern.
Faheem Mitha
Die rcSchale ermöglicht die Verrohrung stderr. Siehe meine Antwort unten.
Rolf
20

Dies ähnelt dem "Temp-File-Trick" von phunehehe, verwendet jedoch stattdessen eine Named-Pipe, so dass Sie die Ergebnisse bei der Ausgabe etwas näher kommen lassen.

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

Bei dieser Konstruktion wird stderr auf die Pipe mit dem Namen "mypipe" gerichtet. Da grepmit einem Datei-Argument aufgerufen wurde, sucht es nicht nach STDIN für seine Eingabe. Leider müssen Sie diese Named Pipe immer noch bereinigen, wenn Sie fertig sind.

Wenn Sie mit Bash 4, gibt es eine Abkürzung Syntax command1 2>&1 | command2, die ist command1 |& command2. Ich glaube jedoch, dass dies eine reine Syntaxverknüpfung ist. Sie leiten STDERR weiterhin an STDOUT um.

Steven D
quelle
Siehe auch
Stefan Lasiewski
1
Diese |&Syntax ist perfekt, um aufgeblähte Ruby-Stderr-Stack-Traces zu bereinigen. Ich kann diese endlich ohne allzu großen Aufwand greifen.
pgr
8

Die Antworten von Gilles und Stefan Lasiewski sind beide gut, aber dieser Weg ist einfacher:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

Ich gehe davon aus, dass Sie nicht wollen, dass etwas ffmpeg'sgedruckt wird.

Wie es funktioniert:

  • zuerst Rohre
    • ffmpeg und grep werden gestartet, wobei die Standardeinstellung von ffmpeg zur Standardeinstellung von grep wechselt
  • Weiterleitungen von links nach rechts
    • Das Stderr von ffmpeg ist auf das Stdout gesetzt (derzeit die Pipe).
    • ffmpegs stdout ist auf / dev / null gesetzt
Mikel
quelle
Diese Beschreibung verwirrt mich. Die letzten beiden Aufzählungszeichen lassen mich denken, "stdout nach stdout umleiten" und dann "stdout (mit stderr jetzt) ​​nach / dev / null umleiten". Dies ist jedoch nicht das, was dies wirklich tut. Diese Aussagen scheinen umgekehrt zu sein.
Steve
1
@Steve Im zweiten Punkt gibt es kein "with stderr". Haben Sie unix.stackexchange.com/questions/37660/order-of-redirections gesehen ?
Mikel,
Nein, ich meine, das ist meine Interpretation dessen, wie Sie es auf Englisch beschrieben haben. Der von Ihnen angegebene Link ist jedoch sehr nützlich.
Steve
Warum musste es nach / dev / null umgeleitet werden?
Kanwaljeet Singh
7

Das in diesen Tests verwendete Skript finden Sie weiter unten.

Grep kann nur mit stdin arbeiten, daher müssen Sie den stderr-Stream in eine Form konvertieren, die Grep parsen kann.

Normalerweise werden sowohl stdout als auch stderr auf Ihrem Bildschirm gedruckt:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

Gehen Sie wie folgt vor, um stdout auszublenden, aber immer noch stderr zu drucken:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

Aber grep funktioniert nicht auf stderr! Sie würden erwarten, dass der folgende Befehl Zeilen unterdrückt, die 'err' enthalten, dies jedoch nicht.

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

Hier ist die Lösung.

Die folgende Bash-Syntax verbirgt die Ausgabe in stdout, zeigt aber immer noch stderr an. Zuerst leiten wir stdout nach / dev / null weiter, dann konvertieren wir stderr nach stdout, da Unix-Pipes nur mit stdout arbeiten. Sie können den Text immer noch lesen.

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(Beachten Sie, dass der obige Befehl dann anders ist ./command >/dev/null 2>&1, was ein sehr häufiger Befehl ist).

Hier ist das zum Testen verwendete Skript. Dies gibt eine Zeile an stdout und eine Zeile an stderr aus:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0
Stefan Lasiewski
quelle
Wenn Sie die Umleitungen umschalten, benötigen Sie nicht alle geschweiften Klammern. Tu es einfach ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel
3

Wenn Sie die Ausgabe eines Befehls an einen anderen |leiten (mit ), leiten Sie nur die Standardausgabe um. Das sollte also erklären, warum

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

gibt nicht aus, was Sie wollten (es funktioniert jedoch).

Wenn Sie die Fehlerausgabe nicht in die Standardausgabe umleiten möchten, können Sie die Fehlerausgabe in eine Datei umleiten und später erneut abrufen

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
phunehehe
quelle
Vielen Dank. it does work, thoughSie meinen, es funktioniert auf Ihrer Maschine? Zweitens können wir, wie Sie mit pipe bereits betont haben, nur stdout umleiten. Ich bin an einem Befehl oder einer Bash-Funktion interessiert, mit der ich stderr umleiten kann. (aber nicht die temporäre Datei Trick)
Andrew-Dufresne
@ Andrew Ich meine, der Befehl funktioniert so, wie er entworfen wurde. Es funktioniert einfach nicht so, wie Sie es wollen :)
Phunehehe
Ich kenne keine Möglichkeit, die Fehlerausgabe eines Befehls an die Standardeingabe eines anderen Befehls umzuleiten. Wäre interessant, wenn jemand darauf hinweisen kann.
Phunehehe
2

Sie können die Streams tauschen. Dies würde es Ihnen ermöglichen, zum grepursprünglichen Standardfehlerstrom zurückzukehren, während Sie weiterhin die Ausgabe erhalten, die ursprünglich zur Standardausgabe im Terminal ging:

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

Dies funktioniert, indem zuerst ein neuer Dateideskriptor (3) erstellt wird, der für die Ausgabe geöffnet ist, und auf den Standardfehlerstrom ( 3>&2) gesetzt wird. Dann leiten wir Standardfehler zur Standardausgabe ( 2>&1) um. Schließlich wird die Standardausgabe auf den ursprünglichen Standardfehler umgeleitet und der neue Dateideskriptor geschlossen ( 1>&3-).

In deinem Fall:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

Testen Sie es:

$ ( 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
Kusalananda
quelle
1

Ich benutze rcin diesem Fall gerne die Shell.

Installieren Sie zuerst das Paket (es ist weniger als 1 MB).

Dies ist ein Beispiel dafür, wie Sie verwerfen stdoutund stderreinlesen würden rc:

find /proc/ >[1] /dev/null |[2] grep task

Sie können es tun, ohne Bash zu verlassen:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

Wie Sie vielleicht bemerkt haben, ist die Syntax einfach und daher meine bevorzugte Lösung.

Sie können angeben, welcher Dateideskriptor in eckigen Klammern direkt nach dem Pipe-Zeichen eingefügt werden soll.

Standard-Dateideskriptoren sind wie folgt nummeriert:

  • 0: Standardeingabe
  • 1: Standardausgang
  • 2: Standardfehler
Rolf
quelle
0

Eine Variation des Bash-Unterprozess-Beispiels:

Echo zwei Zeilen zu stderr und tee stderr zu einer Datei, grep das Tee und Pipe zurück zu stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

Standard:

fff

Einige.Ladefehler (zB stderr):

asdf
fff
jmunsch
quelle
-1

versuche diesen Befehl

  • Datei mit zufälligem Namen erstellen
  • Ausgabe an die Datei senden
  • Inhalt der Datei an Pipe senden
  • grep

ceva -> nur ein Variablenname (englisch = etwas)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;
Rodislav Moldovan
quelle
Ich würde verwenden /tmp/$ceva, besser als das aktuelle Verzeichnis mit temporären Dateien zu verunreinigen - und Sie gehen davon aus, dass das aktuelle Verzeichnis beschreibbar ist.
Rolf