2010-12-21 13 views
9

Zu Testzwecken möchte ich stdout und stderr separat zur Überprüfung durch nachfolgenden Code speichern. Zum Beispiel sollte ein Testlauf mit fehlerhafter Eingabe zur Ausgabe in stderr führen, aber nichts in stdout, während ein Testlauf mit korrekter Eingabe zur Ausgabe von stdout, aber nichts zu stderr führen sollte. Die Einsparung muss synchron sein, um Rennbedingungen mit den Tests zu vermeiden (also kann ich process substitution nicht verwenden).Speichern Sie stdout, stderr und stdout + stderr synchron

Um den Test nach der Tat debuggen zu können, muss ich auch stdout und stderr in der Reihenfolge sehen, die sie ausgegeben wurden. Also muss ich sie entweder in der gleichen Datei/Variable/was auch immer speichern oder sie gleichzeitig an das Terminal senden, um sie getrennt zu speichern.

Um zu testen, welcher Fehler passiert ist, brauche ich den Exit-Code des Befehls.

Aus Gründen der Effizienz und Genauigkeit kann ich natürlich nicht jeden Test zweimal durchlaufen.

Ist es beispielsweise möglich, stdout zu stdout.log, stderr zu stderr.log, und beide zu output.log im selben Befehl umleiten? Oder einen synchronentee Befehl separat für stdout und stderr zu verwenden? Oder um eine Kopie von stdout und stderr in separaten Variablen zu speichern?

aktualisieren: Es sieht aus wie Lösung Tim arbeitet fast (am Terminal zur Ausgabe geändert statt Protokollierung all.log):

mit Ausnahme der letzten Iteration zu arbeiten Dies scheint
$ set -o pipefail 
$ { 
    { 
     echo foo | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 
foo 
$ cat stdout.log 
foo 
$ cat stderr.log 
$ { 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 
foo 
$ cat stdout.log 
$ cat stderr.log 
foo 
$ bar=$({ 
    { 
     echo foo | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1) 
$ echo "$bar" 
foo 
$ cat stdout.log 
foo 
$ cat stderr.log 
$ bar=$({ 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1) 
$ cat stdout.log 
$ cat stderr.log 
foo 
$ echo "$bar" 
foo 

, wobei der Wert von bar wird auf den Inhalt von stderr festgelegt. Irgendwelche Vorschläge für alle diese zu arbeiten?

Antwort

11

Bitte siehe BashFAQ/106. Es ist nicht einfach, deinen Kuchen zu essen und zu essen.

Von dieser Seite:

Aber einige Leute werden auch nicht den Verlust der Trennung zwischen stdout und stderr oder der Desynchronisation von Linien akzeptieren. Sie sind Puristen und fragen nach der schwierigsten Form von allen - ich möchte stdout und stderr in einer einzigen Datei zusammenfassen, ABER ich möchte auch, dass sie ihre ursprünglichen, separaten Ziele beibehalten.

Um dies zu tun, müssen wir zunächst ein paar Notizen machen:

  • Wenn es sein werden zwei separate stdout und stderr Streams, dann hat einige Verfahren jeder von ihnen zu schreiben.
  • Es gibt keine Möglichkeit, einen Prozess im Shell-Skript zu schreiben, der von zwei separaten FDs liest, wenn eine von ihnen eine verfügbare Eingabe hat, weil die Shell keine Abfrage (2) oder Auswahl (2) hat.
  • Daher werden wir zwei separate Writer-Prozesse benötigen.
  • Die einzige Möglichkeit, die Ausgabe von zwei separaten Writern davon abzuhalten, sich gegenseitig zu zerstören, besteht darin, sicherzustellen, dass beide ihre Ausgabe im Append-Modus öffnen. Ein FD, das im Append-Modus geöffnet wird, hat die garantierte Eigenschaft, dass jedes Mal, wenn Daten dorthin geschrieben werden, zuerst zum Ende gesprungen wird.So

:

# Bash 
> mylog 
exec > >(tee -a mylog) 2> >(tee -a mylog >&2) 

echo A >&2 
cat file 
echo B >&2 

Dies stellt sicher, dass die Protokolldatei korrekt ist. Es garantiert nicht, dass die Autoren vor dem nächsten Shell-Prompt beenden:

~$ ./foo 
A 
hi mom 
B 
~$ cat mylog 
A 
hi mom 
B 
~$ ./foo 
A 
hi mom 
~$ B 

Auch sehen BashFAQ/002 und BashFAQ/047.

+1

wir reden zeilenbasierte Strings Angenommen, wir können beide Rohrströme durch, sagen wir, 'sed' Markierungen am Zeilenanfang hinzufügen? Dann könnten wir anhand der Protokolldatei feststellen, welche Zeile aus welchem ​​Stream stammt. – Raphael

+1

@Raphael: Sie sollten in der Lage sein, 'Tee' innerhalb jeder Prozesssubstitution vorauszusetzen, zum Beispiel:' exec>> (sed 's/^/von STDOUT: /' | tee -a mylog) 2>> (sed 's/^/von STDERR:/'| tee -a mylog> & 2) '(ungetestet). Dies würde übrigens dazu führen, dass die Ausgabe an das Terminal auch diese Präfixe aufweist. –

+0

Cool! Verletzt das nicht einen Teil des Zitats, das Sie geben? Jetzt haben wir eine synchronisierte gemeinsame Ausgabedatei und wissen immer noch welche welche war, oder fehlt mir etwas? – Raphael

2

Das klingt wie eine Aufgabe für Multiitee.

für synchrone Ausgabe oder Disk Um zu testen, schreibt ohne Prozess Substitution verwenden Sie möchten mit Bash-Shell-Umleitung Tricks und T-Shirt (1) ähnlich dem folgenden spielen, um:

echos() { echo "hello on stdout"; echo "hello on stderr" 1>&2; return 0; } 

    { 
    { 
    echos 3>&- | 
     tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | 
     tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 2>&1 | tee /dev/stderr > all.log 

open -e stdout.log stderr.log all.log # Mac OS X 

Weitere Informationen finden Sie unter: http://codesnippets.joyent.com/posts/show/8769

um den Exit-Code des Befehls erhalten Sie jeweils prüfen, pipefail oder PIPESTATUS möchten (zusätzlich zu dem zuletzt Exit-Code Variable "$?"):

help set | grep -i pipefail 
man bash | less -p pipefail 
man bash | less -p PIPESTATUS 
+0

PIPESTATUS scheint nicht zu funktionieren - Wenn der Befehl echos 1 zurückgibt, sind es immer noch nur zwei Nullen. – l0b0

+0

'set -o pipefail' funktioniert aber. – l0b0

1

Für die letzte Iteration, wobei der Wert der Bar auf den Inhalt Stderr gesetzt, versuchen:

# restore original stdout & stderr at the end by adding: 1>&2 2>&1 
bar="$(
{ 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 1>&2 2>&1 
)" 

echo "$bar"