EDIT: Ich sehe, ich wurde entgleist und beantwortete eine andere Frage als die gestellte. Die Antwort auf die eigentliche Frage befindet sich am Ende von Paul Tomblins Antwort. (Wenn Sie diese Lösung verbessern möchten, um stdout und stderr aus irgendeinem Grund getrennt umzuleiten, können Sie die hier beschriebene Technik verwenden.)
Ich wollte eine Antwort, die die Unterscheidung zwischen stdout und stderr bewahrt. Leider sind alle bisher gegebenen Antworten, die diese Unterscheidung bewahren, rassenanfällig: Sie riskieren, dass Programme unvollständige Eingaben sehen, wie ich in den Kommentaren ausgeführt habe.
Ich denke, ich habe endlich eine Antwort gefunden, die die Unterscheidung bewahrt, nicht rassenanfällig ist und auch nicht besonders fummelig.
Erster Baustein: stdout und stderr tauschen:
my_command 3>&1 1>&2 2>&3-
Zweiter Baustein: Wenn wir nur stderr filtern wollten (z. B. tee), könnten wir dies erreichen, indem wir stdout & stderr austauschen, filtern und dann zurück tauschen:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Jetzt ist der Rest einfach: Wir können entweder am Anfang einen Standardfilter hinzufügen:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
oder am Ende:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
Um mich davon zu überzeugen, dass beide oben genannten Befehle funktionieren, habe ich Folgendes verwendet:
alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'
Ausgabe ist:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
und meine Eingabeaufforderung kommt teed stderr: to stderr
erwartungsgemäß sofort nach dem " " zurück.
Fußnote zu zsh :
Die obige Lösung funktioniert in Bash (und vielleicht einigen anderen Shells, da bin ich mir nicht sicher), aber in Zsh funktioniert sie nicht. Es gibt zwei Gründe, warum es in zsh fehlschlägt:
- Die Syntax
2>&3-
wird von zsh nicht verstanden. das muss umgeschrieben werden als2>&3 3>&-
- Wenn Sie in zsh (im Gegensatz zu anderen Shells) einen bereits geöffneten Dateideskriptor umleiten, führt er in einigen Fällen (ich verstehe nicht ganz, wie er sich entscheidet) stattdessen ein integriertes tee-ähnliches Verhalten aus. Um dies zu vermeiden, müssen Sie jedes fd schließen, bevor Sie es umleiten.
So muss zum Beispiel meine zweite Lösung für zsh as neu geschrieben werden {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(was auch in bash funktioniert, aber schrecklich ausführlich ist).
Auf der anderen Seite können Sie das mysteriöse integrierte implizite Abschlagen von zsh nutzen, um eine viel kürzere Lösung für zsh zu erhalten, bei der überhaupt kein Abschlag ausgeführt wird:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(Ich hätte aus den Dokumenten nicht erraten, dass das >&1
und das 2>&2
sind, was das implizite Abschlagen von zsh auslöst. Ich habe das durch Ausprobieren herausgefunden.)