COPY von stdout redirect, um Dateien innerhalb des bash-Skripts selbst zu protokollieren

Ich weiß, wie man stdout in eine Datei umleitet :

exec > foo.log echo test 

Dadurch wird der Test in die foo.log-Datei geschrieben.

Jetzt möchte ich die Ausgabe in die Protokolldatei redirect und sie auf stdout behalten

dh es kann trivial von außerhalb des Skripts durchgeführt werden:

 script | tee foo.log 

aber ich möchte es im Skript selbst deklarieren

Ich habe es versucht

 exec | tee foo.log 

aber es hat nicht funktioniert.

   
 #!/usr/bin/env bash # Redirect stdout ( > ) into a named pipe ( >() ) running "tee" exec > >(tee -i logfile.txt) # Without this, only stdout would be captured - ie your # log file would not contain any error messages. # SEE (and upvote) the answer by Adam Spiers, which keeps STDERR # as a separate stream - I did not want to steal from him by simply # adding his answer to mine. exec 2>&1 echo "foo" echo "bar" >&2 

Beachten Sie, dass dies bash , nicht sh . Wenn Sie das Skript mit sh myscript.sh , erhalten Sie einen Fehler in sh myscript.sh eines syntax error near unexpected token '>' .

Wenn Sie mit Signal-Traps arbeiten, sollten Sie die Option tee -i , um eine Unterbrechung der Ausgabe zu vermeiden, wenn ein Signal auftritt. (Danke an JamesThomasMoon1979 für den Kommentar.)


Werkzeuge, die ihre Ausgabe abhängig davon ändern, ob sie in eine Pipe oder ein Terminal schreiben (z. B. mit colors und einer columnisierten Ausgabe), erkennen das obige Konstrukt so, dass sie in eine Pipe ausgegeben werden.

Es gibt Optionen, um das Einfärben / Spalten zu erzwingen (zB ls -C --color=always ). Beachten Sie, dass dies dazu führt, dass die Farbcodes ebenfalls in die Protokolldatei geschrieben werden, wodurch sie weniger lesbar wird.

Die akzeptierte Antwort behält STDERR nicht als separaten Dateideskriptor bei. Das bedeutet

 ./script.sh >/dev/null 

gibt keine bar zum Terminal aus, nur zur Logdatei und

 ./script.sh 2>/dev/null 

gibt sowohl foo als auch bar an das Terminal aus. Natürlich ist das nicht das Verhalten, das ein normaler Benutzer erwarten würde. Dies kann behoben werden, indem zwei separate Abzweigungen verwendet werden, die beide an dieselbe Protokolldatei angehängt sind:

 #!/bin/bash # See (and upvote) the comment by JamesThomasMoon1979 # explaining the use of the -i option to tee. exec > >(tee -ia foo.log) exec 2> >(tee -ia foo.log >&2) echo "foo" echo "bar" >&2 

(Beachten Sie, dass die Protokolldatei anfänglich nicht abgeschnitten wird. Wenn Sie dieses Verhalten wünschen, sollten Sie hinzufügen

 >foo.log 

an den Anfang des Skripts.)

Die POSIX.1-2008-Spezifikation von tee(1) erfordert, dass der Ausgang ungepuffert ist, dh nicht einmal in Zeilen gepuffert ist. In diesem Fall ist es möglich, dass STDOUT und STDERR in der gleichen Zeile von foo.log ; Dies könnte jedoch auch auf dem Terminal passieren, so dass die Protokolldatei eine getreue Wiedergabe dessen ist, was auf dem Terminal zu sehen ist, wenn nicht ein genauer Spiegel davon ist. Wenn Sie die STDOUT-Zeilen sauber von den STDERR-Zeilen trennen möchten, sollten Sie in Erwägung ziehen, zwei Protokolldateien zu verwenden, möglicherweise mit Datumsstempelpräfixen in jeder Zeile, um später eine chronologische Neuzusammenstellung zu ermöglichen.

Lösung für Busybox- und Nicht-Bash-Shells

Die akzeptierte Antwort ist sicherlich die beste Wahl für bash. Ich arbeite in einer Busybox-Umgebung ohne Zugriff auf bash, und es versteht nicht die exec > >(tee log.txt) . Außerdem wird exec >$PIPE ordnungsgemäß ausgeführt, und es wird versucht, eine normale Datei mit dem gleichen Namen wie die Named Pipe zu erstellen, die fehlschlägt und hängen bleibt.

Hoffentlich wäre das nützlich für jemanden, der keine Bash hat.

Auch für jeden, der eine Named Pipe verwendet, ist es sicher, rm $PIPE rmieren, da dadurch die Verbindung zwischen der Pipe und dem VFS getrennt wird, aber die processe, die sie verwenden, behalten eine Referenzzählung bei, bis sie beendet sind.

Beachten Sie, dass die Verwendung von $ * nicht unbedingt sicher ist.

 #!/bin/sh if [ "$SELF_LOGGING" != "1" ] then # The parent process will enter this branch and set up logging # Create a named piped for logging the child's output PIPE=tmp.fifo mkfifo $PIPE # Launch the child process without redirected to the named pipe SELF_LOGGING=1 sh $0 $* >$PIPE & # Save PID of child process PID=$! # Launch tee in a separate process tee logfile < $PIPE & # Unlink $PIPE because the parent process no longer needs it rm $PIPE # Wait for child process running the rest of this script wait $PID # Return the error code from the child process exit $? fi # The rest of the script goes here 

Platziere in der Skriptdatei alle Befehle in Klammern, etwa so:

 ( echo start ls -l echo end ) | tee foo.log 

Einfache Möglichkeit, ein Bash-Skript in syslog zu protokollieren. Die Skriptausgabe ist sowohl über /var/log/syslog als auch über stderr verfügbar. Syslog fügt nützliche Metadaten hinzu, einschließlich Zeitstempeln.

Fügen Sie diese Zeile oben hinzu:

 exec &> >(logger -t myscript -s) 

Alternativ senden Sie das Protokoll an eine separate Datei:

 exec &> >(ts |tee -a /tmp/myscript.output >&2 ) 

Dies erfordert moreutils (für den Befehl ts , der Zeitstempel hinzufügt).

Mit der angenommenen Antwort kam mein Skript immer wieder ungewöhnlich früh (direkt nach ‘exec>> (tee …)’) zurück und ließ den Rest meines Skripts im Hintergrund laufen. Da ich diese Lösung nicht finden konnte, fand ich eine andere Lösung / Problemlösung:

 # Logging setup logfile=mylogfile mkfifo ${logfile}.pipe tee < ${logfile}.pipe $logfile & exec &> ${logfile}.pipe rm ${logfile}.pipe # Rest of my script 

Dadurch wird die Ausgabe des Skripts vom process über die Pipe in den Hintergrundprozeß von ‘tee’ verlagert, der alles auf der Festplatte und im ursprünglichen stdout des Skripts protokolliert.

Beachten Sie, dass “exec &>” sowohl stdout als auch stderr umleitet, wir könnten sie auch einzeln redirect, wenn wir möchten, oder zu “exec>” wechseln, wenn wir nur stdout wollen.

Auch wenn die Pipe zu Beginn des Skripts aus dem Dateisystem entfernt wird, funktioniert sie bis zum Ende der processe. Wir können es einfach nicht mit dem Dateinamen nach der RM-Zeile referenzieren.

Bash 4 hat einen coproc Befehl, der eine benannte Pipe zu einem Befehl erstellt und Ihnen ermöglicht, über ihn zu kommunizieren.

Ich kann nicht sagen, dass ich mich mit einer der auf exec basierenden Lösungen wohl fühle. Ich ziehe es vor, T-Shirt direkt zu verwenden, also lasse ich das Skript sich selbst mit T-Stück nennen, wenn es angefordert wird:

 # my script: check_tee_output() { # copy (append) stdout and stderr to log file if TEE is unset or true if [[ -z $TEE || "$TEE" == true ]]; then echo '-------------------------------------------' >> log.txt echo '***' $(date) $0 $@ >> log.txt TEE=false $0 $@ 2>&1 | tee --append log.txt exit $? fi } check_tee_output $@ rest of my script 

Dies ermöglicht Ihnen Folgendes:

 your_script.sh args # tee TEE=true your_script.sh args # tee TEE=false your_script.sh args # don't tee export TEE=false your_script.sh args # tee 

Sie können dies anpassen, z. B. make tee = false als Standard, TEE stattdessen die Log-Datei speichern usw. Ich denke, diese Lösung ist ähnlich wie jbarlow, aber einfacher, vielleicht hat meine Einschränkungen, denen ich noch nicht begegnet bin.

Keine von beiden ist eine perfekte Lösung, aber hier sind ein paar Dinge, die Sie ausprobieren könnten:

 exec >foo.log tail -f foo.log & # rest of your script 

oder

 PIPE=tmp.fifo mkfifo $PIPE exec >$PIPE tee foo.log < $PIPE & # rest of your script rm $PIPE 

Der zweite würde eine Pipe-Datei hinterlassen, wenn etwas mit Ihrem Skript schief geht, was ein Problem sein kann oder auch nicht (vielleicht könnten Sie es später in der Eltern-Shell finden).