Wie leite ich die Ausgabe eines gesamten Shell-Skripts innerhalb des Skripts selbst um?

205

Ist es möglich, die gesamte Ausgabe eines Bourne-Shell-Skripts an einen anderen Ort umzuleiten, jedoch mit Shell-Befehlen im Skript selbst?

Das Umleiten der Ausgabe eines einzelnen Befehls ist einfach, aber ich möchte mehr davon:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Das heißt: Wenn das Skript nicht interaktiv ausgeführt wird (z. B. cron), speichern Sie die Ausgabe von allem in einer Datei. Wenn Sie interaktiv von einer Shell aus ausführen, lassen Sie die Ausgabe wie gewohnt auf stdout gehen.

Ich möchte dies für ein Skript tun, das normalerweise vom periodischen Dienstprogramm FreeBSD ausgeführt wird. Es ist Teil des täglichen Laufs, den ich normalerweise nicht jeden Tag per E-Mail sehen möchte, daher habe ich ihn nicht gesendet. Wenn jedoch etwas in diesem einen bestimmten Skript fehlschlägt, ist das für mich wichtig und ich möchte in der Lage sein, die Ausgabe dieses einen Teils der täglichen Jobs zu erfassen und per E-Mail zu versenden.

Update: Joshuas Antwort ist genau richtig, aber ich wollte auch stdout und stderr für das gesamte Skript speichern und wiederherstellen, was folgendermaßen gemacht wird:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4
Steve Madsen
quelle
2
Das Testen auf $ TERM ist nicht der beste Weg, um den interaktiven Modus zu testen. Testen Sie stattdessen, ob stdin ein tty ist (test -t 0).
Chris Jester-Young
2
Mit anderen Worten: wenn [! -t 0]; dann exec> somefile 2> & 1; fi
Chris Jester-Young
1
Siehe hier für alle Güte: http://tldp.org/LDP/abs/html/io-redirection.html Grundsätzlich, was von Joshua gesagt wurde. Die Datei exec> leitet stdout in eine bestimmte Datei um, exec <file ersetzt stdin durch Datei usw. Es ist das gleiche wie üblich, verwendet jedoch exec (siehe man exec für weitere Details).
Loki
In Ihrem Update-Bereich sollten Sie auch die FDs 3 und 4 wie exec 1>&3 2>&4 3>&- 4>&-
folgt schließen

Antworten:

173

Beantwortung der Frage als aktualisiert.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Die geschweiften Klammern '{...}' bieten eine Einheit für die E / A-Umleitung. Die geschweiften Klammern müssen dort erscheinen, wo ein Befehl erscheinen könnte - vereinfacht, am Anfang einer Zeile oder nach einem Semikolon. ( Ja, das kann präzisiert werden. Wenn Sie streiten möchten, lassen Sie es mich wissen. )

Sie haben Recht, dass Sie das ursprüngliche stdout und stderr mit den von Ihnen angezeigten Weiterleitungen beibehalten können, aber es ist normalerweise einfacher für die Personen, die das Skript später warten müssen, zu verstehen, was vor sich geht, wenn Sie den umgeleiteten Code wie oben gezeigt in den Geltungsbereich aufnehmen.

Die relevanten Abschnitte des Bash-Handbuchs sind Gruppierungsbefehle und E / A-Umleitung . Die relevanten Abschnitte der POSIX-Shell-Spezifikation sind zusammengesetzte Befehle und E / A-Umleitung . Bash hat einige zusätzliche Notationen, ähnelt aber ansonsten der POSIX-Shell-Spezifikation.

Jonathan Leffler
quelle
3
Dies ist viel klarer als das Speichern der ursprünglichen Deskriptoren und deren spätere Wiederherstellung.
Steve Madsen
23
Ich musste ein bisschen googeln, um zu verstehen, was das wirklich tut, also wollte ich teilen. Die geschweiften Klammern werden zu einem "Codeblock" , der praktisch eine anonyme Funktion erzeugt . Die Ausgabe alles im Codeblock kann dann umgeleitet werden (siehe Beispiel 3-2 von diesem Link). Beachten Sie auch , dass die geschweiften Klammern nicht ein Start Subshell , aber ähnliche I / O - Umleitungen können mit Subshells Verwendung von Klammern erfolgen.
chris
3
Ich mag diese Lösung besser als die anderen. Selbst eine Person mit nur dem grundlegendsten Verständnis der E / A-Umleitung kann verstehen, was passiert. Außerdem ist es ausführlicher. Und als Pythoner liebe ich ausführlich.
John Red
Besser machen >>. Einige Leute haben die Angewohnheit von >. Das Anhängen ist immer sicherer und empfehlenswerter als das Überwinden. Jemand hat eine Anwendung geschrieben, die den Standardkopierbefehl verwendet, um einige Daten an dasselbe Ziel zu exportieren.
NeverMind9
Diese App heißt ccc.bmw71 (.pro) (3C Battery Monitor Widget, früher „Battery Monitor Widget“) und exportiert den Batterieverlauf immer nach /sdcard/bmw_history.txt. Wenn bereits vorhanden, raten Sie mal, ÜBERSCHREIBEN! Dadurch habe ich versehentlich etwas Batterieverlauf verloren. Ich habe das Limit in Tagen von 30 auf eine sehr hohe Zahl festgelegt, wodurch es ungültig wurde und auf 30 zurückgesetzt wurde. Ich wollte den aktuellen Batterieverlauf exportieren, bevor ich das vorhandene Backup importierte. Dann passierte das.
NeverMind9
175

Normalerweise platzieren wir eine davon am oder nahe dem oberen Rand des Skripts. Skripte, die ihre Befehlszeilen analysieren, führen die Umleitung nach dem Parsen durch.

Senden Sie stdout an eine Datei

exec > file

mit stderr

exec > file                                                                      
exec 2>&1

Hängen Sie sowohl stdout als auch stderr an die Datei an

exec >> file
exec 2>&1

Wie Jonathan Leffler in seinem Kommentar erwähnte :

exechat zwei getrennte Jobs. Die erste besteht darin, die aktuell ausgeführte Shell (Skript) durch ein neues Programm zu ersetzen. Das andere ist das Ändern der E / A-Umleitungen in der aktuellen Shell. Dies zeichnet sich dadurch aus, dass kein Argument dafür vorliegt exec.

Joshua
quelle
7
Ich sage, füge am Ende auch 2> & 1 hinzu, nur damit auch stderr erwischt wird. :-)
Chris Jester-Young
7
Wo legst du diese hin? Oben im Skript?
Colan
4
Bei dieser Lösung muss auch die Umleitung zurückgesetzt werden, die das Skript beendet. Die nächste Antwort von Jonathan Leffler ist in diesem Sinne eher "ausfallsicher".
Chuim
6
@ JohnRed: exechat zwei separate Jobs. Eine besteht darin, das aktuelle Skript mit demselben Prozess durch einen anderen Befehl zu ersetzen. Sie geben den anderen Befehl als Argument an exec(und Sie können die E / A-Umleitungen dabei anpassen). Der andere Job besteht darin, die E / A-Umleitungen im aktuellen Shell-Skript zu ändern, ohne es zu ersetzen. Diese Notation zeichnet sich dadurch aus, dass kein Befehl als Argument für vorhanden ist exec. Die Notation in dieser Antwort ist die Variante "Nur E / A" - sie ändert nur die Umleitung und ersetzt nicht das Skript, das ausgeführt wird. (Der setBefehl ist ähnlich vielseitig.)
Jonathan Leffler
18
exec > >(tee -a "logs/logdata.log") 2>&1druckt die Protokolle auf dem Bildschirm und schreibt sie in eine Datei
shriyog
32

Sie können das gesamte Skript wie folgt einrichten:

main_function() {
  do_things_here
}

Dann haben Sie am Ende des Skripts Folgendes:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Alternativ können Sie bei jedem Lauf alles in die Protokolldatei einloggen und es dennoch an stdout ausgeben, indem Sie einfach Folgendes tun:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log
dbguy
quelle
Meinten Sie main_function >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez
Ich benutze gerne main_function in einer solchen Pipe. In diesem Fall gibt Ihr Skript jedoch nicht den ursprünglichen Rückgabewert zurück. Im Bash-Fall sollten Sie beenden und dann 'exit $ {PIPESTATUS [0]}' verwenden.
Rudimeier
7

Zum Speichern des ursprünglichen stdout und stderr können Sie verwenden:

exec [fd number]<&1 
exec [fd number]<&2

Der folgende Code druckt beispielsweise "walla1" und "walla2" in die Protokolldatei ( a.txt), "walla3" in stdout und "walla4" in stderr.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6
Augenleshem
quelle
1
Normalerweise wäre es besser, zu verwenden exec 5>&1und exec 6>&2, die Umleitung der Ausgabe Notation anstatt Eingabeumleitung Notation für die Ausgänge. Sie kommen damit durch, denn wenn das Skript von einem Terminal ausgeführt wird, ist auch die Standardeingabe beschreibbar und sowohl die Standardausgabe als auch der Standardfehler sind aufgrund einer historischen Eigenart lesbar (oder ist es "Laster"?): Das Terminal ist geöffnet für Lesen und Schreiben sowie dieselbe Beschreibung der geöffneten Datei werden für alle drei Standard-E / A-Dateideskriptoren verwendet.
Jonathan Leffler
3
[ -t <&0 ] || exec >> test.log
Dimitar
quelle