Reindirizza stderr e stdout in Bash


679

Voglio reindirizzare sia stdout che stderr di un processo su un singolo file. Come posso farlo in Bash?


2
Vorrei dire che questa è una domanda sorprendentemente utile. Molte persone non sanno come farlo, poiché non devono farlo così frequentemente, e non è il miglior comportamento documentato di Bash.
Robert Wm Ruedisueli,

2
A volte è utile vedere l'output (come al solito) E reindirizzarlo su un file. Vedi la risposta di Marko di seguito. (Lo dico qui perché è facile guardare la prima risposta accettata se è sufficiente per risolvere un problema, ma altre risposte spesso forniscono informazioni utili.)
jvriesem

Risposte:


763

Dai un'occhiata qui . Dovrebbe essere:

yourcommand &>filename

(redirect sia stdoute stderral nome del file).


29
Questa sintassi è obsoleta secondo il Wiki di Bash Hackers . È?
Salman von Abbas,

20
Secondo wiki.bash-hackers.org/scripting/obsolete , sembra essere obsoleto nel senso che non fa parte di POSIX, ma la pagina man di bash non fa menzione del fatto che verrà rimosso da bash nel prossimo futuro. La pagina man specifica una preferenza per '&>' over '> &', che è altrimenti equivalente.
Chepner,

13
Suppongo che non dovremmo usare &> in quanto non è in POSIX e le shell comuni come "trattino" non lo supportano.
Sam Watkins,

27
Un suggerimento in più: se lo usi in uno script, assicurati che inizi con #!/bin/bashanziché anziché #!/bin/sh, poiché in richiede bash.
Tor Klingberg,

8
Oppure & >> per aggiungere invece di sovrascrivere.
Alexander Gonchiy,

449
do_something 2>&1 | tee -a some_file

Questo reindirizzerà stderr su stdout e stdout some_file e lo stamperà su stdout.


16
Su AIX (ksh) la tua soluzione funziona. La risposta accettata do_something &>filenameno. +1.
Ritenuto il

11
@Daniel, ma questa domanda riguarda specificamente bash
John La Rooy,

3
Ho Ambiguous output redirect.idea del perché?
Alexandre Holden Daly,

1
Ho uno script ruby ​​(che non voglio modificare in alcun modo) che stampa i messaggi di errore in grassetto rosso. Questo script ruby ​​viene quindi richiamato dal mio script bash (che posso modificare). Quando uso quanto sopra, stampa i messaggi di errore in testo normale, meno la formattazione. Esiste un modo per mantenere la formattazione su schermo e ottenere l'output (sia stdout che stderr) in un file?
atlantis,

8
Si noti che (per impostazione predefinita) questo ha l'effetto collaterale che $?non si riferisce più allo stato di uscita di do_something, ma allo stato di uscita di tee.
Flimm,

255

Puoi reindirizzare stderr a stdout e lo stdout in un file:

some_command >file.log 2>&1 

Vedi http://tldp.org/LDP/abs/html/io-redirection.html

Questo formato è preferito rispetto al formato più popolare e> che funziona solo in bash. Nella shell Bourne potrebbe essere interpretato come l'esecuzione del comando in background. Inoltre il formato è più leggibile 2 (è STDERR) reindirizzato a 1 (STDOUT).

MODIFICA: ha cambiato l'ordine come indicato nei commenti


47
Questo reindirizza stderr allo stdout originale, non al file dove sta andando stdout. Inserisci '2> & 1' dopo '> file.log' e funziona.

1
Qual è il vantaggio di questo approccio rispetto a some_command &> file.log?
ubermonkey,

6
Se vuoi aggiungere un file, devi farlo in questo modo: echo "pippo" 2> & 1 1 >> bar.txt AFAIK non c'è modo di aggiungere usando &>
SlappyTheFish

9
Argh, sorry, echo "foo" 1 >> bar.txt 2> & 1
SlappyTheFish

11
Penso che l'interpretazione secondo cui 2> & 1 reindirizza stderr a stdout sia sbagliata; Credo che sia più preciso dire che invia stderr nello stesso posto in cui stdout sta andando in questo momento. Quindi posizionare 2> & 1 dopo il primo reindirizzamento è essenziale.
giovedì

201
# 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'"

Ora, l'eco semplice scriverà su $ LOG_FILE. Utile per demonizzare.

All'autore del post originale,

Dipende da cosa devi raggiungere. Se hai solo bisogno di reindirizzare dentro / fuori un comando chiamato dal tuo script, le risposte sono già fornite. Il mio riguarda il reindirizzamento all'interno dello script corrente che influenza tutti i comandi / incorporati (include le forcelle) dopo lo snippet di codice menzionato.


Un'altra soluzione interessante riguarda il reindirizzamento a entrambi std-err / out AND al logger o al file di log in una sola volta che comporta la suddivisione di "un flusso" in due. Questa funzionalità è fornita dal comando 'tee' che può scrivere / aggiungere a più descrittori di file (file, socket, pipe, ecc.) Contemporaneamente: 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
}

Quindi, dall'inizio. Supponiamo di avere un terminale collegato a / dev / stdout (FD # 1) e / dev / stderr (FD # 2). In pratica, potrebbe essere un tubo, una presa o altro.

  • Crea FD n. 3 e n. 4 e punta alla stessa "posizione" rispettivamente n. 1 e n. 2. La modifica di FD # 1 non influirà da ora in poi su FD # 3. Ora, gli FD n. 3 e n. 4 indicano rispettivamente STDOUT e STDERR. Questi saranno usati come vero terminale STDOUT e STDERR.
  • 1>> (...) reindirizza STDOUT per il comando in parentesi
  • parens (sub-shell) esegue la lettura 'tee' dallo STDOUT (pipe) di exec e reindirizza al comando 'logger' tramite un'altra pipe per sub-shell in parens. Allo stesso tempo copia lo stesso input su FD # 3 (terminale)
  • la seconda parte, molto simile, riguarda lo stesso trucco per STDERR e FD n. 2 e n. 4.

Il risultato dell'esecuzione di uno script con la riga sopra e inoltre questo:

echo "Will end up in STDOUT(terminal) and /var/log/messages"

...è come segue:

$ ./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

Se vuoi vedere un'immagine più chiara, aggiungi queste 2 righe allo script:

ls -l /proc/self/fd/
ps xf

1
solo un'eccezione. nel primo esempio che hai scritto: exec 1 <> $ LOG_FILE. causa sempre il file di registro originale owerwritten. per il login reale il modo migliore è: exec 1 >> $ LOG_FILE perché il log viene sempre aggiunto.
Znik,

4
Questo è vero anche se dipende dalle intenzioni. Il mio approccio è creare sempre un file di registro unico e con data e ora. L'altro è aggiungere. Entrambi i modi sono "logrotateable". Preferisco file separati che richiedono meno analisi ma, come ho detto, qualunque cosa faccia galleggiare la tua barca :)
quizac

1
La tua seconda soluzione è informativa, ma cos'è tutto il codice di pulizia? Non sembra rilevante e, in tal caso, confonde solo un esempio altrimenti valido. Mi piacerebbe anche vederlo rielaborato leggermente in modo che gli FD 1 e 2 non vengano reindirizzati al logger ma piuttosto 3 e 4 sono in modo tale che tutto ciò che chiama questo script possa manipolare ulteriormente 1 e 2 sotto il presupposto comune stdout == 1 e stderr == 2, ma la mia breve sperimentazione suggerisce che è più complesso.
JFlo,

1
Mi piace di più con il codice di pulizia. Potrebbe essere un po 'di distrazione dall'esempio principale, ma rimuoverlo renderebbe l'esempio incompleto. La rete è già piena di esempi senza gestione degli errori, o almeno una nota amichevole che ha ancora bisogno di circa un centinaio di righe di codice per essere sicuro da usare.
Zoltan K.

1
Volevo elaborare un codice di pulizia. Fa parte della sceneggiatura che demonizza ergo diventa immune al segnale HANG-UP. 'tee' e 'logger' sono processi generati dallo stesso PPID ed ereditano la trap HUP dallo script bash principale. Quindi, una volta che il processo principale muore, vengono ereditati da init [1]. Non diventeranno zombi (defunti). Il codice di pulizia assicura che tutte le attività in background vengano interrotte, se lo script principale muore. Si applica anche a qualsiasi altro processo che potrebbe essere stato creato e in esecuzione in background.
quizac,

41
bash your_script.sh 1>file.log 2>&1

1>file.logindica alla shell di inviare STDOUT al file file.loge gli 2>&1dice di reindirizzare STDERR (descrittore di file 2) su STDOUT (descrittore di file 1).

Nota: l'ordine è importante, come sottolineato da liw.fi, 2>&1 1>file.lognon funziona.


21

Curiosamente, questo funziona:

yourcommand &> filename

Ma questo dà un errore di sintassi:

yourcommand &>> filename
syntax error near unexpected token `>'

Devi usare:

yourcommand 1>> filename 2>&1

10
&>>sembra funzionare su BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
user272735,

15

Risposta breve: Command >filename 2>&1oCommand &>filename


Spiegazione:

Considera il seguente codice che stampa la parola "stdout" su stdout e la parola "stderror" su stderror.

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

Si noti che l'operatore '&' dice a bash che 2 è un descrittore di file (che punta allo stderr) e non un nome di file. Se avessimo lasciato fuori "&", questo comando sarebbe stampato stdoutsu stdout e avrebbe creato un file chiamato "2" e scritto stderrorlì.

Sperimentando il codice sopra, puoi vedere di persona come funzionano gli operatori di reindirizzamento. Ad esempio, modificando quale file quale dei due descrittori 1,2, viene reindirizzato alle /dev/nullseguenti due righe di codice, elimina tutto da stdout e tutto da stderror rispettivamente (stampa ciò che rimane).

$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout

Ora, possiamo spiegare perché la soluzione per cui il seguente codice non produce alcun output:

(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1

Per capirlo veramente, consiglio vivamente di leggere questa pagina Web nelle tabelle dei descrittori di file . Supponendo che abbiate fatto quella lettura, possiamo procedere. Si noti che i processi di Bash da sinistra a destra; così Bash vede per >/dev/nullprimo (che è lo stesso di 1>/dev/null) e imposta il descrittore di file 1 in modo che punti a / dev / null anziché allo stdout. Fatto ciò, Bash si sposta a destra e vede 2>&1. Questo imposta il descrittore di file 2 in modo che punti allo stesso file del descrittore di file 1 (e non al descrittore di file 1 stesso !!!! (vedere questa risorsa sui puntatoriper maggiori informazioni)) . Poiché il descrittore di file 1 punta a / dev / null e il descrittore di file 2 punta allo stesso file del descrittore di file 1, il descrittore di file 2 ora punta anche a / dev / null. Quindi entrambi i descrittori di file puntano a / dev / null, e questo è il motivo per cui non viene riprodotto alcun output.


Per verificare se capisci davvero il concetto, prova a indovinare l'output quando cambiamo l'ordine di reindirizzamento:

(echo "stdout"; echo "stderror" >&2)  2>&1 >/dev/null

stderror

Il ragionamento qui è che valutando da sinistra a destra, Bash vede 2> & 1, e quindi imposta il descrittore di file 2 in modo che punti allo stesso posto del descrittore di file 1, cioè stdout. Quindi imposta il descrittore di file 1 (ricorda che> / dev / null = 1> / dev / null) in modo che punti> / dev / null, eliminando così tutto ciò che normalmente verrebbe inviato allo standard out. Quindi tutto ciò che ci rimane è quello che non è stato inviato a stdout nella subshell (il codice tra parentesi) - cioè "stderror". La cosa interessante da notare è che anche se 1 è solo un puntatore allo stdout, il reindirizzamento del puntatore da 2 a 1 tramite 2>&1NON forma una catena di puntatori 2 -> 1 -> stdout. In caso affermativo, a seguito del reindirizzamento da 1 a / dev / null, il codice2>&1 >/dev/null darebbe la catena di puntatori 2 -> 1 -> / dev / null, e quindi il codice non genererebbe nulla, in contrasto con quanto visto sopra.


Infine, noterei che esiste un modo più semplice per farlo:

Dalla sezione 3.6.4 qui , vediamo che possiamo usare l'operatore &>per reindirizzare sia stdout che stderr. Quindi, per reindirizzare sia l'output stderr che stdout di qualsiasi comando \dev\null(che elimina l'output), digitiamo semplicemente $ command &> /dev/null o nel caso del mio esempio:

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

Key takeaways:

  • I descrittori di file si comportano come puntatori (sebbene i descrittori di file non siano gli stessi dei puntatori di file)
  • Il reindirizzamento di un descrittore di file "a" a un descrittore di file "b" che punta al file "f", fa sì che il descrittore di file "a" punti nella stessa posizione del descrittore di file b - file "f". NON forma una catena di puntatori a -> b -> f
  • A causa di quanto sopra, l'ordine conta, 2>&1 >/dev/nullè! = >/dev/null 2>&1. Uno genera output e l'altro no!

Dai finalmente un'occhiata a queste grandi risorse:

Documentazione Bash sul reindirizzamento , una spiegazione delle tabelle dei descrittori di file , Introduzione ai puntatori


I descrittori di file (0, 1, 2) sono solo offset in una tabella. Quando si usa 2> & 1 l'effetto è lo slot FD [2] = dup (1), quindi ovunque FD [1] puntava FD [2] ora indica. Quando cambi FD [1] per puntare a / dev / null, allora FD [1] viene cambiato ma non cambia lo slot FD [2] (che punta a stdout). Uso il termine dup () perché è la chiamata di sistema utilizzata per duplicare il descrittore di file.
PatS

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

È correlato: Scrivere stdOut e stderr su syslog.

Quasi funziona, ma non da xinto;


Immagino che non funzioni a causa di "/ dev / fd / 3 Autorizzazione negata". Passare a> & 3 può essere d'aiuto.
quizac,

6

Volevo una soluzione per avere l'output di stdout più stderr scritto in un file di registro e stderr ancora su console. Quindi avevo bisogno di duplicare l'output stderr tramite tee.

Questa è la soluzione che ho trovato:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • Prima scambia stderr e stdout
  • quindi aggiungere lo stdout al file di registro
  • pipe stderr per tee e aggiungerlo anche al file di registro

A proposito, questo non ha funzionato per me (il file di registro è vuoto). | tee non ha alcun effetto. Invece l'ho fatto funzionare usando stackoverflow.com/questions/692000/…
Yaroslav Bulatov

4

Per situazioni, quando è necessario "piping" è possibile utilizzare:

| &

Per esempio:

echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt

o

TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log  ; done |& sort -h

Queste soluzioni basate su bash possono reindirizzare STDOUT e STDERR separatamente (da STDERR di "sort -c" o da STDERR a "sort -h").


1

Modo "più semplice" (bash4 soltanto): ls * 2>&- 1>&-.


1

Le seguenti funzioni possono essere utilizzate per automatizzare il processo di commutazione degli output tra stdout / stderr e un file di log.

#!/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"
    }

Esempio di utilizzo all'interno dello script:

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"

quando uso le tue funzioni e tenta di ripristinare gli output standard ottengo l'eco: errore di scrittura: numero di file errato il reindirizzamento funziona perfettamente ... il ripristino non sembra
thom schumacher

per far funzionare il tuo script ho dovuto commentare queste righe e ho cambiato l'ordine: #exec 1> & - #closes FD 1 (logfile) #exec 2> & - #closes FD 2 (logfile); exec 1> & 3 #restore stdout exec 2> & 4 #restore stderr
thom schumacher

Mi dispiace sentirlo. Non ricevo errori durante l'esecuzione in CentOS 7, bash 4.2.46. Ho annotato il riferimento dove ho ricevuto quei comandi. È: Rif: logan.tw/posts/2016/02/20/open-and-close-files-in-bash
Fernando Fabreti

Sto eseguendo questi comandi su AIX che è probabilmente il motivo. Ho aggiunto un post per la correzione che ho apportato.
Thom schumacher,

1

@ Fernando-fabreti

Aggiungendo ciò che hai fatto, ho leggermente modificato le funzioni e rimosso la chiusura di & - e ha funzionato per me.

    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"

1

In situazioni in cui pensi di usare cose come le exec 2>&1trovo più facile da leggere se possibile riscrivendo il codice usando funzioni bash come questa:

function myfunc(){
  [...]
}

myfunc &>mylog.log

0

Per tcsh, devo usare il seguente comando:

command >& file

Se utilizzato command &> file, verrà visualizzato l'errore "Comando null non valido".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.