Ich möchte sowohl stdout als auch stderr eines Prozesses in eine einzelne Datei umleiten. Wie mache ich das in Bash?
Schau mal hier . Sollte sein:
yourcommand &>filename
(Leitet sowohl stdout
als auch stderr
zum Dateinamen um).
do_something 2>&1 | tee -a some_file
Dies leitet stderr zu stdout und stdout zu some_file
nd print it to stdout um.
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 dem gängigsten &> Format vorgezogen, das nur in Bash funktioniert. In der Bourne-Shell könnte dies so interpretiert werden, dass der Befehl im Hintergrund ausgeführt wird. Auch das Format ist lesbarer 2 (ist STDERR) auf 1 (STDOUT) umgeleitet.
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 für die Dämonisierung.
An den Autor des ursprünglichen Beitrags,
Es kommt darauf an, was Sie erreichen müssen. Wenn Sie nur einen von Ihrem Skript aufgerufenen Befehl umleiten müssen, sind die Antworten bereits gegeben. Bei meinem geht es darum, innerhalb des aktuellen Skripts umzuleiten, das alle Befehle/eingebauten Befehle (einschließlich Gabeln) nach dem genannten Code-Snippet betrifft.
Bei einer anderen coolen Lösung geht es darum, auf std-err/out UND auf logger oder log file gleichzeitig umzuleiten, wobei "ein Stream" in zwei 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 DATEI DATEI2 ...> (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 file.log
zu senden, und 2>&1
weist sie an, STDERR (Dateideskriptor 2) an STDOUT (Dateideskriptor 1) umzuleiten.
Hinweis: Die Reihenfolge ist wichtig, da 2>&1 1>file.log
nicht funktioniert.
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
Kurze Antwort: Command >filename 2>&1
oder Command &>filename
Erläuterung:
Betrachten Sie den folgenden Code, der das Wort "stdout" zu stdout und das Wort "stderror" zu stderror druckt.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Beachten Sie, dass der Operator '&' bash mitteilt, dass 2 ein Dateideskriptor (der auf das stderr zeigt) und kein Dateiname ist. Wenn wir das '&' weglassen, druckt dieser Befehl stdout
nach stdout und erstellt eine Datei mit dem Namen "2" und schreibt stderror
dort.
Wenn Sie mit dem obigen Code experimentieren, können Sie sich selbst davon überzeugen, wie Umleitungsoperatoren funktionieren. Wenn Sie zum Beispiel ändern, welche der beiden Deskriptoren 1,2
zu /dev/null
umgeleitet wird, löschen die folgenden beiden Codezeilen alles aus dem stdout bzw. alles aus dem stderror (was bleibt wird gedruckt).
$ (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, dies zu lesen Webseite über Dateideskriptortabellen . Angenommen, Sie haben diese Lektüre durchgeführt, können wir fortfahren. Beachten Sie, dass Bash von links nach rechts verarbeitet wird. Bash sieht also zuerst >/dev/null
(das ist das Gleiche wie 1>/dev/null
) und setzt den Dateideskriptor 1 so, dass er auf/dev/null anstatt auf stdout zeigt. Danach bewegt sich Bash nach rechts und sieht 2>&1
. Dadurch wird der Dateideskriptor 2 so eingestellt, dass er auf dieselbe Datei verweist wie Dateideskriptor 1 (und nicht auf Dateideskriptor 1 selbst !!!!) (siehe - diese Ressource auf Zeigern für weitere Informationen)). Da der Dateideskriptor 1 auf/dev/null zeigt und der Dateideskriptor 2 auf dieselbe Datei wie der Dateideskriptor 1 zeigt, zeigt der Dateideskriptor 2 jetzt auch auf/dev/null. Somit 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 daher den Dateideskriptor 2 so einstellt, dass er auf dieselbe Stelle verweist wie Dateideskriptor 1, dh stdout. Anschließend wird der Dateideskriptor 1 (denken Sie daran, dass>/dev/null = 1>/dev/null) so eingestellt, dass er auf>/dev/null zeigt, wodurch alles gelöscht wird, was normalerweise an den Standardausgang gesendet wird. Somit bleibt uns nur das übrig, was nicht in der Subshell an stdout gesendet wurde (der Code in Klammern) - d. H. "Stderror". Das Interessante daran ist, dass, obwohl 1 nur ein Zeiger auf die Standardausgabe ist, das Umleiten von Zeiger 2 auf 1 über 2>&1
KEINE Kette von Zeigern 2 -> 1 -> Standardausgabe bildet. Wenn dies der Fall wäre, würde der Code 2>&1 >/dev/null
als Ergebnis der Umleitung von 1 nach/dev/null die Zeigerkette 2 -> 1 ->/dev/null ergeben, und somit würde der Code im Gegensatz zu nichts erzeugen was wir oben gesehen haben.
Abschließend möchte ich darauf hinweisen, dass es einen einfacheren Weg gibt, dies zu tun:
In Abschnitt 3.6.4 hier sehen wir, dass wir den Operator &>
verwenden können, um sowohl stdout als auch stderr umzuleiten. Um also die Ausgabe von stderr und stdout eines Befehls an \dev\null
umzuleiten (wodurch die Ausgabe gelöscht wird), geben Sie einfach $ command &> /dev/null
ein, oder in meinem Beispiel:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Die zentralen Thesen:
2>&1 >/dev/null
ist! = >/dev/null 2>&1
. Einer erzeugt Output 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 mit: 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 immer noch auf der Konsole. Also musste ich die stderr-Ausgabe über 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 "Verrohrung" erforderlich ist, 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 bashbasierten Lösungen können STDOUT und STDERR getrennt leiten (von STDERR mit "sort -c" oder von STDERR mit "sort -h").
In Situationen, in denen Sie die Verwendung von Dingen wie exec 2>&1
in Betracht ziehen, finde ich es einfacher, wenn möglich, Code mit Bash-Funktionen wie den folgenden umzuschreiben:
function myfunc(){
[...]
}
myfunc &>mylog.log
"Einfachster" Weg (nur bash4): ls * 2>&- 1>&-
.
Die folgenden Funktionen können verwendet werden, um das Umschalten der 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"
Für tcsh muss ich den folgenden Befehl verwenden:
command >& file
Wenn Sie command &> file
verwenden, wird der Fehler "Ungültiger Nullbefehl" ausgegeben.
@ 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"