Wie schreibe ich stderr in eine Datei, während ich "tee" mit einer Pipe verwende?

542

Ich weiß , wie zu verwenden , teedie Ausgabe (schreiben STDOUT) von aaa.shzu bbb.out, während sie noch im Terminal angezeigt wird :

./aaa.sh | tee bbb.out

Wie würde ich jetzt auch STDERRin eine Datei mit dem Namen schreiben ccc.out, während sie noch angezeigt wird?

jparanich
quelle
2
Zur Verdeutlichung: Möchten Sie, dass stderr sowohl zum Bildschirm als auch zur Datei wechselt?
Charles Duffy
Ich habe meinen Beitrag bearbeitet, um dies zu verdeutlichen. Ich glaube, dass Lhunaths Lösung ausreichen wird. Vielen Dank für die Hilfe an alle!
Jparanich

Antworten:

785

Ich gehe davon aus, dass Sie STDERR und STDOUT weiterhin auf dem Terminal sehen möchten. Sie könnten sich für Josh Kelleys Antwort entscheiden, aber ich finde es tailsehr hackisch und ungeschickt , ein Hintergrundbild im Hintergrund zu haben, das Ihre Protokolldatei ausgibt. Beachten Sie, wie Sie ein Exra-FD behalten und anschließend bereinigen müssen, indem Sie es töten, und technisch sollten Sie dies in einem tun trap '...' EXIT.

Es gibt einen besseren Weg, dies zu tun, und Sie haben es bereits entdeckt : tee.

Nur, anstatt es nur für Ihr stdout zu verwenden, haben Sie ein T-Stück für stdout und eines für stderr. Wie werden Sie dies erreichen? Prozessersetzung und Dateiumleitung:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Lassen Sie es uns aufteilen und erklären:

> >(..)

>(...)(Prozessersetzung) erstellt ein FIFO und lässt es teeabhören. Anschließend wird >(STDOUT) mithilfe von (Dateiumleitung) commandan das FIFO umgeleitet, das Ihr erster Benutzer abhört tee.

Gleiches gilt für die Sekunde:

2> >(tee -a stderr.log >&2)

Wir verwenden die Prozessersetzung erneut, um einen teeProzess zu erstellen, der aus STDIN liest und in diesen speichert stderr.log. teegibt seinen Eingang wieder auf STDOUT aus, aber da sein Eingang unser STDERR ist, möchten wir den teeSTDOUT wieder auf unseren STDERR umleiten . Dann verwenden wir die Dateiumleitung , um commandden STDERR zum FIFO-Eingang ( teeSTDIN) umzuleiten.

Siehe http://mywiki.wooledge.org/BashGuide/InputAndOutput

Die Prozessersetzung ist eines der wirklich schönen Dinge, die Sie als Bonus für die Auswahl bashIhrer Shell im Gegensatz zu sh(POSIX oder Bourne) erhalten.


In shmüssten Sie die Dinge manuell erledigen:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"
lhunath
quelle
5
Ich habe es versucht: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)was funktioniert, wartet aber auf Eingabe. Gibt es einen einfachen Grund, warum dies passiert?
Justin
1
@SillyFreak Ich verstehe nicht, was du tun willst oder was das Problem ist, das du hast. Echotest; exit erzeugt auf stdout keine Ausgabe, daher bleibt err leer.
lhunath
1
danke für diesen Kommentar; Ich habe herausgefunden, was mein logischer Fehler danach war: Wenn Bash als interaktive Shell aufgerufen wird, gibt es eine Eingabeaufforderung aus und gibt den Ausgang zu stderr aus. Wenn stderr jedoch umgeleitet wird, wird bash standardmäßig als nicht interaktiv gestartet. Vergleiche /bin/bash 2> errund/bin/bash -i 2> err
Silly Freak
15
Und für diejenigen, die "sehen ist glauben", ein schneller Test:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Matthew Wilcoxson
2
Wow . Gute Antwort: "Lassen Sie es uns aufteilen und erklären" +1
Jalanb
672

warum nicht einfach:

./aaa.sh 2>&1 | tee -a log

Dies leitet einfach stderrzu weiter stdout, sodass das Tee sowohl zum Protokollieren als auch zum Screenen wiedergegeben wird. Vielleicht fehlt mir etwas, weil einige der anderen Lösungen wirklich kompliziert erscheinen.

Hinweis: Seit Bash Version 4 können Sie |&als Abkürzung verwenden für 2>&1 |:

./aaa.sh |& tee -a log
user542833
quelle
85
Dies funktioniert einwandfrei, wenn sowohl stdout (Kanal 1) als auch stderr (Kanal 2) in derselben Datei protokolliert werden sollen (eine einzelne Datei, die die Mischung aus stdout und sterr enthält). Die andere, kompliziertere Lösung ermöglicht es Ihnen, stdout und stderr in zwei verschiedene Dateien zu trennen (stdout.log bzw. stderr.log). Manchmal ist das wichtig, manchmal nicht.
Tyler Rick
19
Die anderen Lösungen sind in vielen Fällen weitaus komplizierter als nötig. Dieser funktioniert perfekt für mich.
Dkamins
13
Das Problem bei dieser Methode ist, dass Sie den Exit- / Statuscode aus dem aaa.sh-Prozess verlieren, was wichtig sein kann (z. B. bei Verwendung in einem Makefile). Sie haben dieses Problem mit der akzeptierten Antwort nicht.
Stefaan
9
Wenn es Ihnen nichts ausmacht, stdout / stderr zusammenzuführen, ./aaa.sh |& tee aaa.logfunktioniert es (in bash).
JFS
5
@Stefaan Ich glaube, Sie können den Exit-Status beibehalten, wenn Sie der Befehlskette set -o pipefailgefolgt von gefolgt von ;oder &&wenn ich mich nicht irre.
David
59

Dies kann nützlich sein, wenn Sie dies über Google finden. Kommentieren Sie einfach das Beispiel aus, das Sie ausprobieren möchten. Natürlich können Sie die Ausgabedateien auch umbenennen.

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}
Anthony
quelle
6
Nein, und ich denke, exec kann einige Erklärungen gebrauchen. exec >bedeutet, das Ziel eines Dateideskriptors an ein bestimmtes Ziel zu verschieben. Der Standardwert ist 1, daher wird exec > /dev/nulldie Ausgabe von stdout in dieser Sitzung von nun an nach / dev / null verschoben. Die aktuellen Dateideskriptoren für diese Sitzung können durch Ausführen angezeigt werden ls -l /dev/fd/. Versuch es! Sehen Sie dann, was passiert, wenn Sie ausgeben. exec 2>/tmp/stderr.log.Außerdem exec 3>&1bedeutet dies, dass Sie einen neuen Dateideskriptor mit der Nummer 3 erstellen und ihn zum Ziel von Dateideskriptor 1 umleiten. Im Beispiel war das Ziel der Bildschirm, auf dem der Befehl ausgegeben wurde.
Trommelfeuer
Das Beispiel, um stdout und stderr sowohl auf dem Bildschirm als auch in separaten Dateien anzuzeigen, ist fantastisch !!! Vielen Dank!
Marcello de Sales
21

Um stderr in eine Datei umzuleiten, zeigen Sie stdout auf dem Bildschirm an und speichern Sie stdout in einer Datei:

./aaa.sh 2> ccc.out | Tee ./bbb.out

BEARBEITEN : Um sowohl stderr als auch stdout auf dem Bildschirm anzuzeigen und beide in einer Datei zu speichern, können Sie die E / A-Umleitung von bash verwenden :

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1
Josh Kelley
quelle
1
Ich gehe davon aus, dass der Benutzer möchte, dass stderr zusätzlich zur Datei zu seiner Konsole wechselt, obwohl dies nicht explizit angegeben wurde.
Charles Duffy
2
Ich hätte klarer sein sollen, ich wollte auch stderr auf den Bildschirm. Ich habe Josh Kelleys Lösung immer noch genossen, aber ich finde, dass Lhunath mehr zu meinen Bedürfnissen passt. Danke Leute!
Jparanich
18

Mit anderen Worten, Sie möchten stdout in einen Filter ( tee bbb.out) und stderr in einen anderen Filter ( tee ccc.out) leiten . Es gibt keine Standardmethode, um etwas anderes als stdout in einen anderen Befehl zu leiten, aber Sie können dies umgehen, indem Sie Dateideskriptoren jonglieren.

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

Siehe auch Wie grep ich einen Standardfehlerstrom (stderr)? und wann würden Sie einen zusätzlichen Dateideskriptor verwenden?

In bash (und ksh und zsh), aber nicht in anderen POSIX-Shells wie dash, können Sie die Prozessersetzung verwenden :

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

Beachten Sie, dass dieser Befehl in bash zurückgegeben wird, sobald er ./aaa.shbeendet ist, auch wenn die teeBefehle noch ausgeführt werden (ksh und zsh warten auf die Unterprozesse). Dies kann ein Problem sein, wenn Sie so etwas tun ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. Verwenden Sie in diesem Fall stattdessen den Dateideskriptor Jonglieren oder ksh / zsh.

Gilles 'SO - hör auf böse zu sein'
quelle
4
Dies scheint die einzige Antwort zu sein, die es erlaubt, die stdout / stderr-Streams unverändert zu lassen (z. B. sie nicht zusammenzuführen). Süss!
user1338062
Der einfachste Ansatz für shCron-Jobs, bei denen keine Prozessersetzung verfügbar ist.
Roger Dueck
13

Bei Verwendung von Bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

Das Guthaben (das nicht von oben beantwortet wird) finden Sie hier: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html

ChristopheD
quelle
5

In meinem Fall wurde auf einem Skript ein Befehl ausgeführt, während sowohl stdout als auch stderr in eine Datei umgeleitet wurden.

cmd > log 2>&1

Ich musste es so aktualisieren, dass bei einem Fehler einige Aktionen basierend auf den Fehlermeldungen ausgeführt werden. Ich könnte natürlich das Dup entfernen 2>&1und das stderr aus dem Skript erfassen, aber dann werden die Fehlermeldungen nicht als Referenz in die Protokolldatei aufgenommen. Während die akzeptierte Antwort von @lhunath dasselbe tun soll, leitet sie um stdoutund stderrzu verschiedenen Dateien, was nicht das ist, was ich will, aber es hat mir geholfen, die genaue Lösung zu finden, die ich brauche:

(cmd 2> >(tee /dev/stderr)) > log

Mit dem oben genannten hat log eine Kopie von beiden stdoutund stderrich kann stderraus meinem Skript erfassen, ohne mir Sorgen machen zu müssen stdout.

haridsv
quelle
4

Das Folgende funktioniert für KornShell (ksh), wo die Prozessersetzung nicht verfügbar ist:

# create a combined(stdin and stdout) collector
exec 3 <> combined.log

# stream stderr instead of stdout to tee, while draining all stdout to the collector
./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3

# cleanup collector
exec 3>&-

Der eigentliche Trick hier ist die Reihenfolge der 2>&1 1>&3in unserem Fall der Umleitungen stderrauf stdoutund leitet die stdoutzu Descriptor 3. Zu diesem Zeitpunkt sind die stderrund stdoutnoch nicht kombiniert.

Tatsächlich wird das stderr(as stdin) an die Stelle übergeben, teean der es sich anmeldet, stderr.logund es wird auch zu Deskriptor 3 umgeleitet.

Und der Deskriptor 3protokolliert es combined.logständig. Das combined.logenthält also beide stdoutund stderr.

Shuva
quelle
Raffiniert! Schade, dass das unterschätzt wird, weil es eine gute Option ist. Ich KSH haben, btw :)
runlevel0
2

Wenn Sie zsh verwenden , können Sie mehrere Umleitungen verwenden, sodass Sie nicht einmal Folgendes benötigen tee:

./cmd 1>&1 2>&2 1>out_file 2>err_file

Hier leiten Sie einfach jeden Stream zu sich selbst und zur Zieldatei um.


Vollständiges Beispiel

% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err

Beachten Sie, dass hierfür die MULTIOSOption festgelegt werden muss (dies ist die Standardeinstellung).

MULTIOS

Führen Sie implizite tees oder cats durch, wenn mehrere Umleitungen versucht werden (siehe Umleitung ).

Arminius
quelle