it-swarm.com.de

Leiten Sie stderr und stdout in Bash um

Ich möchte sowohl stdout als auch stderr eines Prozesses in eine einzelne Datei umleiten. Wie mache ich das in Bash?

625
flybywire

Schau mal hier . Sollte sein:

yourcommand &>filename

(Leitet sowohl stdout als auch stderr zum Dateinamen um).

714
dirkgently
do_something 2>&1 | tee -a some_file

Dies leitet stderr zu stdout und stdout zu some_filend print it to stdout um.

429
Marko

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

231
f3lix
# 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.

  • Erstellen Sie die FDs Nr. 3 und Nr. 4 und zeigen Sie auf den gleichen "Ort" wie Nr. 1 bzw. Nr. 2. Das Ändern von FD # 1 wirkt sich ab sofort nicht mehr auf FD # 3 aus. Die FDs 3 und 4 zeigen nun auf STDOUT bzw. STDERR. Diese werden als real Terminal STDOUT und STDERR verwendet.
  • 1>> (...) leitet STDOUT zum Befehl in Parens um
  • parens (Sub-Shell) führt das 'tee'-Lesen von execs STDOUT (Pipe) aus und leitet den Befehl' logger 'über eine andere Pipe an sub-Shell in parens weiter. Gleichzeitig wird derselbe Eingang auf FD # 3 (Terminal) kopiert.
  • der zweite, sehr ähnliche Teil befasst sich mit dem gleichen Trick für STDERR und FDs # 2 und # 4.

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
185
quizac
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.

37
Guðmundur H

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
21
Mark

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:

  • Dateideskriptoren verhalten sich wie Zeiger (obwohl Dateideskriptoren nicht mit Dateizeigern identisch sind)
  • Das Umleiten eines Dateideskriptors "a" zu einem Dateideskriptor "b", der auf die Datei "f" zeigt, bewirkt, dass der Dateideskriptor "a" auf dieselbe Stelle verweist wie der Dateideskriptor b - die Datei "f". Es bildet KEINE Kette von Zeigern a -> b -> f
  • Aus diesem Grund ist die Reihenfolge wichtig, 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

13
Evan Rosica
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; (

11
log-control

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
  • Zuerst stderr und stdout tauschen
  • hängen Sie dann die Standardausgabe an die Protokolldatei an
  • führe stderr zum tee und hänge es auch an die log datei an
6
bebbo

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").

4

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
1
user2761220

"Einfachster" Weg (nur bash4): ls * 2>&- 1>&-.

1
reim

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"
1

Für tcsh muss ich den folgenden Befehl verwenden:

command >& file

Wenn Sie command &> file verwenden, wird der Fehler "Ungültiger Nullbefehl" ausgegeben.

0
einstein6

@ 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"
0
thom schumacher