Leiten Sie COPY of stdout aus dem Bash-Skript selbst in die Protokolldatei um

235

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

exec > foo.log
echo test

Dadurch wird der 'Test' in die Datei foo.log eingefügt.

Jetzt möchte ich die Ausgabe in die Protokolldatei umleiten UND auf stdout belassen

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.

Vitaly Kushner
quelle
3
Ihre Frage ist schlecht formuliert. Wenn Sie 'exec> foo.log' aufrufen, ist die Standardausgabe des Skripts die Datei foo.log. Ich glaube , Sie bedeuten , dass Sie die Ausgabe gehen foo.log und zum tty wollen, da zu foo.log gehen wird nach stdout geht.
William Pursell
Was ich tun möchte, ist die | zu verwenden auf der 'exec'. das wäre perfekt für mich, dh "exec | tee foo.log", leider können Sie keine Pipe-Umleitung für den exec-Aufruf verwenden
Vitaly Kushner

Antworten:

297
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. 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 bashnicht der Fall ist sh. Wenn Sie das Skript mit aufrufen sh myscript.sh, wird ein Fehler in der Art von angezeigt syntax error near unexpected token '>'.

Wenn Sie mit Signalfallen arbeiten, möchten Sie möglicherweise die tee -iOption verwenden, um eine Unterbrechung des Ausgangs zu vermeiden, wenn ein Signal auftritt. (Danke an JamesThomasMoon1979 für den Kommentar.)


Werkzeuge, die ihre Ausgabe ändern, je nachdem, ob sie in eine Pipe oder ein Terminal schreiben ( lsz. B. mithilfe von Farben und Spaltenausgabe), erkennen das obige Konstrukt so, dass sie in eine Pipe ausgeben.

Es gibt Optionen zum Erzwingen des Kolorierens / Kolumnierens (z ls -C --color=always. B. ). Beachten Sie, dass dies dazu führt, dass die Farbcodes ebenfalls in die Protokolldatei geschrieben werden, wodurch diese weniger lesbar wird.

DevSolar
quelle
5
Tee wird auf den meisten Systemen gepuffert, sodass die Ausgabe möglicherweise erst nach Abschluss des Skripts eintrifft. Da dieses Tee in einer Subshell und nicht in einem untergeordneten Prozess ausgeführt wird, kann wait nicht zum Synchronisieren der Ausgabe mit dem aufrufenden Prozess verwendet werden. Was Sie wollen, ist eine ungepufferte Version von Tee ähnlich bogomips.org/rainbows.git/commit/…
14
@Barry: POSIX gibt an, dass teedie Ausgabe nicht gepuffert werden soll. Wenn es auf den meisten Systemen puffert, ist es auf den meisten Systemen defekt. Das ist ein Problem der teeImplementierungen, nicht meiner Lösung.
DevSolar
3
@ Sebastian: execist sehr mächtig, aber auch sehr engagiert. Sie können das aktuelle Standardout auf einem anderen Dateiskriptor "sichern" und später wiederherstellen. Google "Bash Exec Tutorial", es gibt viele fortgeschrittene Sachen da draußen.
DevSolar
2
@AdamSpiers: Ich bin mir auch nicht sicher, worum es bei Barry ging. Bash's execist dokumentiert, um keine neuen Prozesse zu starten, >(tee ...)ist ein Standard namens Pipe / Process Substitution, und das &in der Umleitung hat natürlich nichts mit Hintergrund zu tun ...? :-)
DevSolar
11
Ich schlage vor, -izu gehen tee. Andernfalls stören Signalinterrupts (Traps) stdout im Hauptskript. Wenn Sie beispielsweise eine haben trap 'echo foo' EXITund dann drücken ctrl+c, wird " foo " nicht angezeigt . Also würde ich die Antwort auf ändern exec &> >(tee -ia file).
JamesThomasMoon1979
173

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

./script.sh >/dev/null

wird nicht baran das Terminal ausgegeben , sondern nur an die Protokolldatei, und

./script.sh 2>/dev/null

gibt beide foound baran das Terminal aus. Dies ist eindeutig nicht das Verhalten, das ein normaler Benutzer wahrscheinlich erwartet. Dies kann behoben werden, indem zwei separate Tee-Prozesse verwendet werden, die beide an dieselbe Protokolldatei angehängt werden:

#!/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 das oben Gesagte die Protokolldatei zunächst nicht abschneidet. Wenn Sie dieses Verhalten wünschen, sollten Sie es hinzufügen

>foo.log

an den Anfang des Skripts.)

Die POSIX.1-2008-Spezifikation vontee(1) verlangt, dass die Ausgabe ungepuffert ist, dh nicht einmal zeilengepuffert. In diesem Fall ist es also möglich, dass STDOUT und STDERR in derselben Zeile von enden foo.log. jedoch könnte das auch auf dem Terminal passieren, so dass die Protokolldatei ein getreues Abbild der sein wird , was könnte auf dem Terminal zu sehen ist, wenn kein exakter Spiegel davon. Wenn Sie möchten, dass die STDOUT-Zeilen sauber von den STDERR-Zeilen getrennt werden, sollten Sie zwei Protokolldateien verwenden, möglicherweise mit Datumsstempelpräfixen in jeder Zeile, um später einen chronologischen Zusammenbau zu ermöglichen.

Adam Spires
quelle
Aus irgendeinem Grund bleiben in meinem Fall, wenn das Skript über einen Aufruf von c-program system () ausgeführt wird, die beiden Tee-Unterprozesse auch nach dem Beenden des Hauptskripts bestehen. Also musste ich Fallen wie diese hinzufügen:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
Alveko
15
Ich schlage vor, -izu gehen tee. Andernfalls stören Signalunterbrechungen (Traps) stdout im Skript. Wenn Sie beispielsweise trap 'echo foo' EXITdrücken und dann drücken ctrl+c, wird " foo " nicht angezeigt . Also würde ich die Antwort auf ändern exec > >(tee -ia foo.log).
JamesThomasMoon1979
Darauf aufbauend habe ich einige kleine "Sourcing" -Skripte erstellt. Kann sie in einem Skript wie . logoder verwenden . log foo.log: sam.nipl.net/sh/log sam.nipl.net/sh/log-a
Sam Watkins
1
Das Problem bei dieser Methode besteht darin, dass Nachrichten STDOUTzuerst als Stapel und dann Nachrichten angezeigt werden STDERR. Sie sind nicht wie gewöhnlich verschachtelt.
CMCDragonkai
28

Lösung für Busybox-, MacOS Bash- und Non-Bash-Shells

Die akzeptierte Antwort ist sicherlich die beste Wahl für Bash. Ich arbeite in einer Busybox-Umgebung ohne Zugriff auf Bash und verstehe die exec > >(tee log.txt)Syntax nicht. Es funktioniert auch nicht exec >$PIPErichtig und versucht, eine normale Datei mit demselben Namen wie die Named Pipe zu erstellen, die fehlschlägt und hängt.

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

Für jeden, der eine rm $PIPENamed Pipe verwendet, ist dies sicher , da dadurch die Verknüpfung der Pipe mit dem VFS aufgehoben wird. Die Prozesse, die sie verwenden, behalten jedoch bis zu ihrem Abschluss eine Referenzanzahl bei.

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 with stdout 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, which is 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
jbarlow
quelle
Dies ist die einzige Lösung, die ich bisher gesehen habe und die auf einem Mac funktioniert
Mike Baglio Jr.
19

Fügen Sie in Ihrer Skriptdatei alle Befehle wie folgt in Klammern ein:

(
echo start
ls -l
echo end
) | tee foo.log
WReach
quelle
5
pedantisch, könnte auch Klammern verwenden ( {})
Glenn Jackman
Nun ja, das habe ich mir überlegt, aber dies ist keine Umleitung des aktuellen Shell-Standards, es ist eine Art Cheat. Sie führen tatsächlich eine Subshell aus und führen eine regelmäßige Piper-Umleitung darauf durch. arbeitet gedacht. Ich bin gespalten mit dieser und der "tail -f foo.log &" Lösung. Ich werde ein wenig warten, um zu sehen, ob eine bessere Oberfläche auftaucht. wenn nicht wahrscheinlich siedeln;)
Vitaly Kushner
8
{} führt eine Liste in der aktuellen Shell-Umgebung aus. () führt eine Liste in einer Subshell-Umgebung aus.
Verdammt. Danke dir. Die akzeptierte Antwort dort oben hat bei mir nicht funktioniert. Ich habe versucht, ein Skript so zu planen, dass es unter MingW auf einem Windows-System ausgeführt wird. Ich glaube, es hat sich über eine nicht implementierte Prozesssubstitution beschwert. Diese Antwort funktionierte nach der folgenden Änderung einwandfrei, um sowohl stderr als auch stdout zu erfassen: `` `-) | tee foo.log +) 2> & 1 | tee foo.log
Jon Carter
14

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

Fügen Sie diese Zeile oben hinzu:

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

Alternativ können Sie das Protokoll in eine separate Datei senden:

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

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

Tobu
quelle
10

Mit der akzeptierten Antwort kehrte mein Skript immer wieder außergewöhnlich früh zurück (direkt nach 'exec >> (tee ...)') und ließ den Rest meines Skripts im Hintergrund laufen. Da ich diese Lösung nicht auf meine Weise zum Laufen bringen konnte, fand ich eine andere Lösung / Problemumgehung für das Problem:

# 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 Prozess über die Pipe in den Sub-Hintergrundprozess von 'tee' geleitet, der alles auf der Disc und im ursprünglichen Standard des Skripts protokolliert.

Beachten Sie, dass 'exec &>' sowohl stdout als auch stderr umleitet. Wir können sie separat umleiten, wenn wir möchten, oder zu 'exec>' wechseln, wenn wir nur stdout möchten.

Auch wenn die Pipe zu Beginn des Skripts aus dem Dateisystem entfernt wird, funktioniert sie weiter, bis der Vorgang abgeschlossen ist. Wir können es einfach nicht mit dem Dateinamen nach der rm-Zeile referenzieren.

fgunger
quelle
Ähnliche Antwort als zweite Idee von David Z . Schauen Sie sich die Kommentare an. +1 ;-)
Olibre
Funktioniert gut. Ich verstehe den $logfileTeil von nicht tee < ${logfile}.pipe $logfile &. Insbesondere habe ich versucht, dies zu ändern, um vollständig erweiterte Befehlsprotokollzeilen (von set -x) in die Datei zu erfassen , während nur Zeilen ohne führendes '+' in stdout angezeigt wurden, indem ich zu geändert habe, (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &aber eine Fehlermeldung bezüglich erhalten habe $logfile. Können Sie die teeZeile etwas genauer erklären ?
Chris Johnson
Ich habe dies getestet und es scheint, dass diese Antwort STDERR nicht beibehält (sie wird mit STDOUT zusammengeführt). Wenn Sie sich also darauf verlassen, dass die Streams für die Fehlererkennung oder andere Umleitung getrennt sind, sollten Sie sich Adams Antwort ansehen.
HeroCC
2

Bash 4 verfügt über einen coprocBefehl, der eine Named Pipe für einen Befehl erstellt und die Kommunikation über diesen Befehl ermöglicht.

Bis auf weiteres angehalten.
quelle
1

Ich kann nicht sagen, dass ich mit einer der auf exec basierenden Lösungen zufrieden bin. Ich bevorzuge die direkte Verwendung von tee, daher rufe ich das Skript auf Anfrage selbst mit tee auf:

# 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. tee = false als Standard festlegen, TEE stattdessen die Protokolldatei halten lassen usw. Ich denke, diese Lösung ähnelt der von jbarlow, ist aber einfacher, vielleicht hat meine Lösung Einschränkungen, auf die ich noch nicht gestoßen bin.

Oliver
quelle
-1

Beides ist keine perfekte Lösung, aber hier sind einige 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

Die zweite würde eine Pipe-Datei herumliegen lassen, wenn etwas mit Ihrem Skript schief geht, was ein Problem sein kann oder nicht (dh Sie könnten rmes später in der übergeordneten Shell tun).

David Z.
quelle
1
tail hinterlässt einen laufenden Prozess im 2. Skript tee blockiert, oder Sie müssen ihn mit & ausführen. In diesem Fall verlässt es den Prozess wie im 1. Skript.
Vitaly Kushner
@Vitaly: Ups, teeich habe den Hintergrund vergessen - ich habe bearbeitet. Wie gesagt, beides ist keine perfekte Lösung, aber die Hintergrundprozesse werden beendet, wenn die übergeordnete Shell beendet wird, sodass Sie sich keine Sorgen machen müssen, dass sie Ressourcen für immer belasten.
David Z
1
Huch: Diese sehen ansprechend aus, aber die Ausgabe von tail -f geht auch an foo.log. Sie können dies beheben, indem Sie tail -f vor dem exec ausführen, aber das tail bleibt nach dem Beenden des übergeordneten Elements weiterhin ausgeführt. Sie müssen es explizit töten, wahrscheinlich in einer Falle 0.
William Pursell
Ja. Wenn das Skript im Hintergrund ausgeführt wird, bleiben die Prozesse überall.