Wie kann ich stderr und nicht stdout leiten?

981

Ich habe ein Programm, das Informationen in stdoutund schreibt stderr, und ich muss grepdurchgehen, was zu stderr kommt , während ich stdout ignoriere .

Ich kann es natürlich in 2 Schritten tun:

command > /dev/null 2> temp.file
grep 'something' temp.file

aber ich würde es vorziehen, dies ohne temporäre Dateien tun zu können. Gibt es irgendwelche Tricks mit intelligenten Rohrleitungen?

Peter Mortensen
quelle
Eine ähnliche Frage, aber unter Beibehaltung von stdout: unix.stackexchange.com/questions/3514/…
joeytwiddle
Diese Frage war für Bash, aber es lohnt sich, diesen verwandten Artikel für die Bourne / Almquist-Shell zu erwähnen .
Stephen Niedzielski
10
Ich hatte so etwas erwartet : command 2| othercommand. Bash ist so perfekt, dass die Entwicklung 1982 endete, also werden wir das leider nie in Bash sehen.
Rolf

Antworten:

1189

Leiten Sie zuerst stderr zu stdout um - das Rohr; leiten Sie dann stdout weiter /dev/null(ohne zu ändern, wohin stderr geht):

command 2>&1 >/dev/null | grep 'something'

Einzelheiten zur E / A-Umleitung in all ihren Varianten finden Sie im Kapitel zu Umleitungen im Bash-Referenzhandbuch.

Beachten Sie, dass die Reihenfolge der E / A-Umleitungen von links nach rechts interpretiert wird, aber Pipes eingerichtet werden, bevor die E / A-Umleitungen interpretiert werden. Dateideskriptoren wie 1 und 2 verweisen auf offene Dateibeschreibungen. Die Operation 2>&1bewirkt, dass sich Dateideskriptor 2 alias stderr auf dieselbe geöffnete Dateibeschreibung bezieht, auf die sich Dateideskriptor 1 alias stdout derzeit bezieht (siehe dup2()und open()). Die Operation >/dev/nulländert dann den Dateideskriptor 1 so, dass er auf eine offene Dateibeschreibung für verweist /dev/null, aber das ändert nichts an der Tatsache, dass der Dateideskriptor 2 auf die offene Dateibeschreibung verweist, auf die der Dateideskriptor 1 ursprünglich zeigte - nämlich auf die Pipe.

Jonathan Leffler
quelle
44
Ich bin neulich über / dev / stdout / dev / stderr / dev / stdin gestolpert und war neugierig, ob das gute Möglichkeiten sind, dasselbe zu tun? Ich dachte immer, 2> & 1 wäre etwas verschleiert. Also so etwas wie: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons
17
Sie könnten /dev/stdoutet al. Oder verwenden /dev/fd/N. Sie sind geringfügig weniger effizient, es sei denn, die Shell behandelt sie als Sonderfälle. Die reine numerische Notation beinhaltet nicht den Zugriff auf Dateien nach Namen, aber die Verwendung der Geräte bedeutet eine Suche nach Dateinamen. Ob Sie das messen können, ist umstritten. Ich mag die Prägnanz der numerischen Notation - aber ich benutze sie schon so lange (mehr als ein Vierteljahrhundert; autsch!), Dass ich nicht qualifiziert bin, ihre Vorzüge in der modernen Welt zu beurteilen.
Jonathan Leffler
23
@ Jonathan Leffler: Ich nehme ein kleines Problem mit Ihrer Klartext-Erklärung 'Stderr nach stdout umleiten und dann nach / dev / null umleiten ' - Da man Umleitungsketten von rechts nach links lesen muss (nicht von links nach rechts), wir sollte auch unsere Erklärung im Klartext daran anpassen: 'Stdout nach / dev / null umleiten und dann stderr dorthin, wo sich stdout befand' .
Kurt Pfeifle
116
@ KurtPfeifle: au contraire! Man muss die Umleitungsketten von links nach rechts lesen, da die Shell sie so verarbeitet. Die erste Operation ist die 2>&1, dh "verbinde stderr mit dem Dateideskriptor, zu dem stdout gerade geht". Die zweite Operation ist 'stdout so ändern, dass es zu /dev/null' geht , wobei stderr zum ursprünglichen stdout, der Pipe, übergeht . Die Shell teilt zuerst die Elemente am Pipe-Symbol auf, sodass die Pipe-Umleitung vor der 2>&1oder >/dev/null-Umleitung erfolgt, aber das ist alles. Die anderen Operationen sind von links nach rechts. (Von rechts nach links würde nicht funktionieren.)
Jonathan Leffler
14
Das, was mich wirklich überrascht, ist, dass es auch unter Windows funktioniert (nach dem Umbenennen /dev/nullin das Windows-Äquivalent nul).
Michael Burr
364

Oder verwenden Sie: Um die Ausgabe von Standardfehler und Standardausgabe zu vertauschen:

command 3>&1 1>&2 2>&3

Dies erstellt einen neuen Dateideskriptor (3) und weist ihn derselben Stelle wie 1 (Standardausgabe) zu, weist dann fd 1 (Standardausgabe) derselben Stelle wie fd 2 (Standardfehler) zu und weist schließlich fd 2 (Standardfehler) zu ) an die gleiche Stelle wie fd 3 (Standardausgabe).

Der Standardfehler ist jetzt als Standardausgabe verfügbar und die alte Standardausgabe bleibt im Standardfehler erhalten. Dies mag übertrieben sein, gibt aber hoffentlich mehr Details zu Bash-Dateideskriptoren (für jeden Prozess stehen neun zur Verfügung).

Kramish
quelle
100
Eine letzte Änderung wäre, 3>&-den Ersatzdeskriptor zu schließen, den Sie aus stdout erstellt haben
Jonathan Leffler
1
Können wir einen Dateideskriptor erstellen, der hat, stderrund einen anderen, der die Kombination von stderrund hat stdout? Mit anderen Worten, können stderrzwei verschiedene Dateien gleichzeitig aufgerufen werden?
Stuart
Im Folgenden werden weiterhin Fehler an stdout gedruckt. Was vermisse ich? ls -l not_a_file 3> & 1 1> & 2 2> & 3> error.txt
user48956
1
@ JonasDahlbæk: Die Optimierung ist in erster Linie ein Problem der Ordnung. In wirklich arkanen Situationen kann es den Unterschied zwischen einer Prozesserkennung und einer Nichterkennung von EOF ausmachen, dies erfordert jedoch sehr besondere Umstände.
Jonathan Leffler
1
Achtung : Dies setzt voraus, dass FD 3 noch nicht verwendet wird, schließt es nicht und macht das Austauschen der Dateideskriptoren 1 und 2 nicht rückgängig, sodass Sie dies nicht an einen anderen 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
218

In Bash können Sie mithilfe der Prozessersetzung auch zu einer Subshell umleiten :

command > >(stdlog pipe)  2> >(stderr pipe)

Für den vorliegenden Fall:

command 2> >(grep 'something') >/dev/null
Rich Johnson
quelle
1
Funktioniert sehr gut für die Ausgabe auf dem Bildschirm. Haben Sie eine Idee, warum der nicht aufbereitete Inhalt erneut angezeigt wird, wenn ich die grep-Ausgabe in eine Datei umleitung? Nach command 2> >(grep 'something' > grep.log)grep.log enthält die gleiche Ausgabe wie ungrepped.log voncommand 2> ungrepped.log
Tim
9
Verwenden Sie 2> >(stderr pipe >&2). Andernfalls wird die Ausgabe der "stderr-Pipe" durch die "stdlog-Pipe" geleitet.
Ceving
yeah!, 2> >(...)funktioniert, ich habe es versucht, 2>&1 > >(...)aber es hat nicht
funktioniert
Hier ist ein kleines Beispiel, das mir beim nächsten Nachschlagen helfen kann. Beachten Sie Folgendes ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) In diesem Fall wollte ich auch sehen, was als Fehler auf meiner Konsole herauskam. Aber STDOUT ging zur Ausgabedatei. Innerhalb der Sub-Shell müssen Sie STDOUT also in den Klammern zurück zu STDERR umleiten. Währenddessen wird die STDOUT-Ausgabe des teeBefehls am Ende der out-content.txtDatei beendet. Das scheint mir widersprüchlich.
wird
@datdinhquoc Ich habe es irgendwie gemacht2>&1 1> >(dest pipe)
Alireza Mohamadi
195

Kombinieren Sie die besten dieser Antworten, wenn Sie Folgendes tun:

command 2> >(grep -v something 1>&2)

... dann bleibt alles stdout als stdout und alles stderr als stderr erhalten, aber in stderr werden keine Zeilen mit der Zeichenfolge "etwas" angezeigt.

Dies hat den einzigartigen Vorteil, dass stdout und stderr nicht umgekehrt oder verworfen, nicht zusammengefügt oder temporäre Dateien verwendet werden.

Pinko
quelle
Ist command 2> >(grep -v something)(ohne 1>&2) nicht dasselbe?
Francesc Rosas
11
Nein, ohne das wird der gefilterte stderr an stdout weitergeleitet.
Pinko
1
Dies ist, was ich brauchte - tar gibt immer "Datei geändert, während wir sie lesen" für ein Verzeichnis aus, also möchte ich nur diese eine Zeile herausfiltern, aber sehen, ob andere Fehler auftreten. Sollte also tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)funktionieren.
Razzed
Es ist falsch zu sagen "mit der Zeichenfolge beginnen". Es gibt nichts an der präsentierten Syntax von grep, was dazu führt, dass nur Zeilen ausgeschlossen werden, die mit der angegebenen Zeichenfolge beginnen. Zeilen werden ausgeschlossen, wenn sie die angegebene Zeichenfolge an einer beliebigen Stelle enthalten.
Mike Nakis
@ MikeNakis danke - behoben! (Das war ein Überbleibsel aus meinem ursprünglichen Antwortentwurf, in dem es Sinn machte ...)
Pinko
102

Es ist viel einfacher, Dinge zu visualisieren, wenn Sie darüber nachdenken, was mit "Weiterleitungen" und "Pipes" wirklich los ist. Weiterleitungen und Pipes in bash bewirken eines: Ändern Sie, wohin die Prozessdateideskriptoren 0, 1 und 2 zeigen (siehe / proc / [pid] / fd / *).

Wenn ein Rohr oder "|" Der Operator ist in der Befehlszeile vorhanden. Als Erstes erstellt bash ein Fifo und zeigt die FD 1 des Befehls auf der linken Seite auf dieses Fifo und zeigt die FD 0 des Befehls auf der rechten Seite auf dasselbe Fifo.

Als nächstes werden die Umleitungsoperatoren für jede Seite von links nach rechts ausgewertet , und die aktuellen Einstellungen werden verwendet, wenn eine Duplizierung des Deskriptors auftritt. Dies ist wichtig, da FD1 (linke Seite) und FD0 (rechte Seite) seit dem ersten Einrichten der Pipe bereits von dem geändert wurden, was sie normalerweise gewesen sein könnten, und jede Verdoppelung dieser wird diese Tatsache widerspiegeln.

Wenn Sie also Folgendes eingeben:

command 2>&1 >/dev/null | grep 'something'

Folgendes passiert in der Reihenfolge:

  1. Eine Pipe (Fifo) wird erstellt. "Befehl FD1" zeigt auf diese Pipe. "grep FD0" zeigt ebenfalls auf diese Pipe
  2. "Befehl FD2" zeigt auf die Stelle, an der "Befehl FD1" aktuell zeigt (die Pipe)
  3. "Befehl FD1" zeigt auf / dev / null

Alle Ausgaben, die "command" in seinen FD 2 (stderr) schreibt, gelangen in die Pipe und werden von "grep" auf der anderen Seite gelesen. Alle Ausgaben, die "Befehl" in seine FD 1 (stdout) schreibt, gelangen nach / dev / null.

Wenn Sie stattdessen Folgendes ausführen:

command >/dev/null 2>&1 | grep 'something'

Folgendes passiert:

  1. Eine Pipe wird erstellt und "Befehl FD 1" und "grep FD 0" werden darauf gezeigt
  2. "Befehl FD 1" zeigt auf / dev / null
  3. "Befehl FD 2" zeigt auf die Stelle, an der FD 1 aktuell zeigt (/ dev / null)

Also gehen alle stdout und stderr von "command" nach / dev / null. Es geht nichts in die Pipe, und daher wird "grep" geschlossen, ohne dass etwas auf dem Bildschirm angezeigt wird.

Beachten Sie auch, dass Weiterleitungen (Dateideskriptoren) schreibgeschützt (<), schreibgeschützt (>) oder schreibgeschützt (<>) sein können.

Eine letzte Anmerkung. Ob ein Programm etwas in FD1 oder FD2 schreibt, liegt ganz beim Programmierer. Die gute Programmierpraxis schreibt vor, dass Fehlermeldungen an FD 2 und die normale Ausgabe an FD 1 gesendet werden müssen. Sie werden jedoch häufig eine schlampige Programmierung finden, die beide vermischt oder die Konvention auf andere Weise ignoriert.

Michael Martinez
quelle
6
Wirklich schöne Antwort. Mein einziger Vorschlag wäre, Ihre erste Verwendung von "fifo" durch "fifo (eine Named Pipe)" zu ersetzen. Ich benutze Linux schon eine Weile, habe es aber irgendwie nie geschafft zu lernen, dass dies ein anderer Begriff für Named Pipe ist. Das hätte mich davor bewahrt, es nachzuschlagen, aber andererseits hätte ich die anderen Dinge, die ich gesehen habe, als ich das herausgefunden habe, nicht gelernt!
Mark Edington
3
@MarkEdington Bitte beachten Sie, dass FIFO nur ein anderer Begriff für Named Pipe im Zusammenhang mit Pipes und IPC ist . In einem allgemeineren Kontext bedeutet FIFO First In, First Out, was das Einfügen und Entfernen aus einer Warteschlangendatenstruktur beschreibt.
Loomchild
5
@Loomchild Natürlich. Der Punkt meines Kommentars war, dass ich selbst als erfahrener Entwickler FIFO noch nie als Synonym für Named Pipe gesehen hatte. Mit anderen Worten, ich wusste das nicht: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Die Klarstellung, dass die Antwort mir Zeit gespart hätte.
Mark Edington
39

Wenn Sie Bash verwenden, verwenden Sie:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines

Ken Sharp
quelle
4
Nein, |&ist gleich, 2>&1was stdout und stderr kombiniert. Die Frage wurde explizit für die Ausgabe ohne stdout gestellt.
Profpatsch
3
„Wenn '| &' verwendet wird, wird der Standardfehler von Befehl1 über die Pipe mit dem Standardeingang von Befehl2 verbunden. es ist eine Abkürzung für 2> & 1 | ” Aus dem vierten Absatz Ihres Links wörtlich entnommen.
Profpatsch
9
@Profpatsch: Kens Antwort ist richtig. Sehen Sie, dass er stdout auf null umleitet, bevor er stdout und stderr kombiniert. Sie erhalten also nur das stderr in der Pipe, da stdout zuvor in / dev / null abgelegt wurde.
Luciano
3
Aber ich fand immer noch, dass Ihre Antwort falsch ist, >/dev/null |&erweitern Sie >/dev/null 2>&1 | und bedeutet, dass stdout inode leer ist, um zu leiten, weil niemand (# 1 # 2 beide an / dev / null inode gebunden ist) an stdout inode gebunden ist (zB ls -R /tmp/* >/dev/null 2>&1 | grep iwird leer geben, aber ls -R /tmp/* 2>&1 >/dev/null | grep ilässt # 2, die an stdout inode gebunden ist, wird pfeifen).
Obst
3
Ken Sharp habe ich getestet und ( echo out; echo err >&2 ) >/dev/null |& grep "."gibt keine Ausgabe (wo wir "err" wollen). man bashsagt, wenn | & verwendet wird ... ist eine Abkürzung für 2> & 1 |. Diese implizite Umleitung des Standardfehlers zur Standardausgabe erfolgt nach allen durch den Befehl angegebenen Umleitungen. Also leiten wir zuerst FD1 des Befehls auf null um, dann leiten wir FD2 des Befehls auf die Stelle um, auf die FD1 zeigte, d. H. null, also erhält greps FD0 keine Eingabe. Weitere Informationen finden Sie unter stackoverflow.com/a/18342079/69663 .
Unhammer
11

Für diejenigen, die stdout und stderr dauerhaft in Dateien umleiten möchten, grep auf stderr, aber behalten Sie das stdout bei, um Nachrichten an ein tty zu schreiben:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3
JBD
quelle
6

Dadurch wird Befehl1 stderr zu Befehl2 stdin umgeleitet, während Befehl1 stdout unverändert bleibt.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Entnommen aus LDP

der Delfin
quelle
2

Ich habe gerade eine Lösung gefunden , um mithilfe von Named Pipes stdoutan einen Befehl und stderran einen anderen zu senden .

Hier geht.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

Es ist wahrscheinlich eine gute Idee, die genannten Rohre anschließend zu entfernen.

Tripp Kinetics
quelle
0

Sie können die RC-Shell verwenden .

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

Dies ist ein Beispiel dafür , wie Sie die Standardausgabe und Rohrstandardfehler verwerfen würde grep in 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, können Sie mithilfe von Klammern nach der Pipe angeben, welcher Dateideskriptor weitergeleitet werden soll.

Standard-Dateideskriptoren sind als solche nummeriert:

  • 0: Standardeingabe
  • 1: Standardausgabe
  • 2: Standardfehler
Rolf
quelle
-3

Ich versuche zu folgen, finde es auch funktionieren,

command > /dev/null 2>&1 | grep 'something'
Lasteye
quelle
Funktioniert nicht Es sendet nur stderr an das Terminal. Ignoriert das Rohr.
Tripp Kinetics