Ich möchte sowohl stdout als auch stderr eines Prozesses in eine einzelne Datei umleiten. Wie mache ich das in Bash?
Ich möchte sowohl stdout als auch stderr eines Prozesses in eine einzelne Datei umleiten. Wie mache ich das in Bash?
Antworten:
Schauen Sie hier . Sollte sein:
yourcommand &>filename
(leitet beide stdout
und stderr
zum Dateinamen um).
#!/bin/bash
anstatt beginnt #!/bin/sh
, da in bash erforderlich ist.
do_something 2>&1 | tee -a some_file
Das wird stderr nach stdout und stdout umleiten some_file
und druckt es nach stdout.
do_something &>filename
nicht. +1.
Ambiguous output redirect.
Idee warum?
$?
nicht mehr auf den Exit-Status von do_something
, sondern auf den Exit-Status von bezieht tee
.
Sie können stderr zu stdout und stdout in eine Datei umleiten :
some_command >file.log 2>&1
Siehe http://tldp.org/LDP/abs/html/io-redirection.html
Dieses Format wird gegenüber dem beliebtesten &> Format bevorzugt, das nur in Bash funktioniert. In der Bourne-Shell kann dies so interpretiert werden, dass der Befehl im Hintergrund ausgeführt wird. Auch das Format ist besser lesbar 2 (ist STDERR) umgeleitet auf 1 (STDOUT).
BEARBEITEN: Die Reihenfolge wurde geändert, wie in den Kommentaren angegeben
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
Jetzt schreibt einfaches Echo in $ LOG_FILE. Nützlich zum Dämonisieren.
An den Autor des ursprünglichen Beitrags,
Es kommt darauf an, was Sie erreichen müssen. Wenn Sie nur einen Befehl umleiten müssen, den Sie von Ihrem Skript aus aufrufen, sind die Antworten bereits gegeben. Bei mir geht es um die Umleitung innerhalb des aktuellen Skripts, die alle Befehle / integrierten Funktionen (einschließlich Gabeln) nach dem genannten Code-Snippet betrifft.
Eine andere coole Lösung besteht darin, auf std-err / out UND gleichzeitig auf logger oder log file umzuleiten, wobei "ein Stream" in zwei Teile geteilt wird. Diese Funktionalität wird durch den Befehl 'tee' bereitgestellt, mit dem mehrere Dateideskriptoren (Dateien, Sockets, Pipes usw.) gleichzeitig geschrieben / angehängt werden können: tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Also von Anfang an. Nehmen wir an, wir haben ein Terminal mit / dev / stdout (FD # 1) und / dev / stderr (FD # 2) verbunden. In der Praxis kann es sich um ein Rohr, eine Muffe oder was auch immer handeln.
Das Ergebnis der Ausführung eines Skripts mit der obigen Zeile und zusätzlich dieser:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
...ist wie folgt:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Wenn Sie ein klareres Bild sehen möchten, fügen Sie dem Skript die folgenden 2 Zeilen hinzu:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
Weist die Shell an, STDOUT an die Datei zu senden file.log
, und 2>&1
weist sie an, STDERR (Dateideskriptor 2) an STDOUT (Dateideskriptor 1) umzuleiten.
Hinweis: Die Reihenfolge ist wichtig, wie liw.fi hervorhob, 2>&1 1>file.log
funktioniert nicht.
Seltsamerweise funktioniert dies:
yourcommand &> filename
Dies führt jedoch zu einem Syntaxfehler:
yourcommand &>> filename
syntax error near unexpected token `>'
Sie müssen verwenden:
yourcommand 1>> filename 2>&1
&>>
scheint auf BASH 4 zu funktionieren:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Kurze Antwort: Command >filename 2>&1
oderCommand &>filename
Erläuterung:
Betrachten Sie den folgenden Code, der das Wort "stdout" in stdout und das Wort "stderror" in stderror druckt.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Beachten Sie, dass der Operator '&' bash mitteilt, dass 2 ein Dateideskriptor ist (der auf den stderr zeigt) und kein Dateiname. Wenn wir das '&' stdout
weglassen, wird dieser Befehl in stdout gedruckt und eine Datei mit dem Namen "2" erstellt und stderror
dort geschrieben.
Wenn Sie mit dem obigen Code experimentieren, können Sie selbst genau sehen, wie Umleitungsoperatoren funktionieren. Wenn Sie beispielsweise ändern, welche Datei welcher der beiden Deskriptoren 1,2
in /dev/null
die folgenden zwei Codezeilen umgeleitet wird, löschen Sie alles aus dem stdout bzw. alles aus dem stderror (Drucken der verbleibenden).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Jetzt können wir erklären, warum die Lösung, warum der folgende Code keine Ausgabe erzeugt:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Um dies wirklich zu verstehen, empfehle ich Ihnen dringend, diese Webseite in Dateideskriptortabellen zu lesen . Angenommen, Sie haben diese Lektüre durchgeführt, können wir fortfahren. Beachten Sie, dass Bash von links nach rechts verarbeitet wird. Daher sieht Bash >/dev/null
zuerst (was dasselbe ist wie 1>/dev/null
) und setzt den Dateideskriptor 1 so, dass er auf / dev / null anstelle von stdout zeigt. Nachdem dies geschehen ist, bewegt sich Bash nach rechts und sieht 2>&1
. Dadurch wird der Dateideskriptor 2 so eingestellt , dass er auf dieselbe Datei wie der Dateideskriptor 1 verweist (und nicht auf den Dateideskriptor 1 selbst !!!! (siehe diese Ressource zu Zeigern)Für mehr Information)) . Da Dateideskriptor 1 auf / dev / null zeigt und Dateideskriptor 2 auf dieselbe Datei wie Dateideskriptor 1 zeigt, zeigt Dateideskriptor 2 jetzt auch auf / dev / null. Daher zeigen beide Dateideskriptoren auf / dev / null, weshalb keine Ausgabe gerendert wird.
Um zu testen, ob Sie das Konzept wirklich verstehen, versuchen Sie, die Ausgabe zu erraten, wenn wir die Umleitungsreihenfolge ändern:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
Standardfehler
Der Grund hierfür ist, dass Bash bei der Auswertung von links nach rechts 2> & 1 sieht und somit den Dateideskriptor 2 so einstellt, dass er auf dieselbe Stelle wie der Dateideskriptor 1 zeigt, dh stdout. Anschließend wird der Dateideskriptor 1 (denken Sie daran, dass> / dev / null = 1> / dev / null) auf> / dev / null verweist, wodurch alles gelöscht wird, was normalerweise an den Standardausgang gesendet wird. Wir haben also nur noch das übrig, was nicht an stdout in der Subshell gesendet wurde (der Code in Klammern) - dh "stderror". Das Interessante dabei ist, dass, obwohl 1 nur ein Zeiger auf das Standardout ist, das Umleiten des Zeigers 2 auf 1 über 2>&1
NICHT eine Kette von Zeigern 2 -> 1 -> Standardout bildet. Wenn dies der Fall ist, wird der Code durch Umleiten von 1 nach / dev / null weitergeleitet2>&1 >/dev/null
würde der Zeigerkette 2 -> 1 -> / dev / null geben, und somit würde der Code im Gegensatz zu dem, was wir oben gesehen haben, nichts erzeugen.
Abschließend möchte ich darauf hinweisen, dass es einen einfacheren Weg gibt, dies zu tun:
In Abschnitt 3.6.4 sehen wir, dass wir den Operator verwenden können &>
, um sowohl stdout als auch stderr umzuleiten. Um also sowohl die stderr- als auch die stdout-Ausgabe eines Befehls an umzuleiten \dev\null
(wodurch die Ausgabe gelöscht wird), geben wir einfach
$ command &> /dev/null
oder in meinem Beispiel Folgendes ein:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Die zentralen Thesen:
2>&1 >/dev/null
ist die Reihenfolge wichtig ! = >/dev/null 2>&1
. Einer erzeugt Ausgabe und der andere nicht!Schauen Sie sich zum Schluss diese großartigen Ressourcen an:
Bash-Dokumentation zur Umleitung , Erläuterung der Dateideskriptortabellen , Einführung in Zeiger
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
Es ist verwandt: Schreiben von stdOut & stderr in syslog.
Es funktioniert fast, aber nicht von xinted;
Ich wollte eine Lösung, um die Ausgabe von stdout plus stderr in eine Protokolldatei zu schreiben und stderr noch auf der Konsole zu haben. Also musste ich die stderr-Ausgabe per Tee duplizieren.
Dies ist die Lösung, die ich gefunden habe:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
In Situationen, in denen "Rohrleitungen" erforderlich sind, können Sie Folgendes verwenden:
| &
Zum Beispiel:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
oder
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Diese bash-basierten Lösungen können STDOUT und STDERR getrennt weiterleiten (von STDERR von "sort -c" oder von STDERR bis "sort -h").
Die folgenden Funktionen können verwendet werden, um das Umschalten von Ausgaben zwischen stdout / stderr und einer Protokolldatei zu automatisieren.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Anwendungsbeispiel im Skript:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
@ Fernando-Fabreti
Zusätzlich zu dem, was Sie getan haben, habe ich die Funktionen leicht geändert und das & - Schließen entfernt, und es hat bei mir funktioniert.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
In Situationen, in denen Sie die Verwendung von Dingen in Betracht ziehen, exec 2>&1
finde ich es einfacher, Code nach Möglichkeit mit Bash-Funktionen wie diesen neu zu schreiben:
function myfunc(){
[...]
}
myfunc &>mylog.log