Reindirizza stderr e stdout in Bash

Voglio redirect sia stdout che stderr di un processo in un singolo file. Come faccio a farlo in Bash?

Dai un’occhiata qui . Dovrebbe essere:

yourcommand &>filename 

(reindirizza sia stdout che stderr al nome file).

 do_something 2>&1 | tee -a some_file 

Questo sta per redirect stderr su stdout e stdout su some_file e stamparlo su stdout.

È ansible redirect lo stderr allo 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 che funziona solo in bash. Nella shell Bourne potrebbe essere interpretato come esecuzione del comando in background. Anche il formato è più leggibile 2 (è STDERR) reindirizzato a 1 (STDOUT).

EDIT: ha cambiato l’ordine come indicato nei commenti

 # 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, echo semplice scriverà su $ LOG_FILE. Utile per demonizzare.

All’autore del post originale,

Dipende da ciò che è necessario ottenere. Se hai solo bisogno di redirect dentro / fuori un comando che chiami dal tuo script, le risposte sono già date. Il mio è il reindirizzamento all’interno dello script corrente che riguarda tutti i comandi / built-in (include i fork) dopo il cnippet di codice menzionato.


Un’altra soluzione interessante riguarda il reindirizzamento sia a std-err / out AND al logger o al file di registro in una volta, il che implica la divisione di “uno stream” in due. Questa funzionalità è fornita dal comando “tee” che può scrivere / accodare più descrittori di file (file, socket, pipe, ecc.) Contemporaneamente: tile 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 connesso a / dev / stdout (FD # 1) e / dev / stderr (FD # 2). In pratica, potrebbe essere una pipa, una presa o qualsiasi altra cosa.

  • Crea FD # 3 e # 4 e punta alla stessa “posizione” rispettivamente di # 1 e # 2. D’ora in poi il cambiamento dell’FD # 1 non ha alcun effetto sull’FD # 3. Ora, gli FD # 3 e # 4 puntano rispettivamente su STDOUT e STDERR. Questi saranno usati come terminali reali STDOUT e STDERR.
  • 1>> (…) reindirizza STDOUT al comando in Parens
  • paren (sub-shell) esegue ‘tee’ leggendo da STDOUT (pipe) di exec e reindirizza al comando ‘logger’ tramite un’altra pipe a 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 FDs # 2 e # 4.

Il risultato dell’esecuzione di uno script con la riga sopra e in aggiunta 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 
 bash your_script.sh 1>file.log 2>&1 

1>file.log ordina alla shell di inviare STDOUT al file file.log , e 2>&1 dice di redirect STDERR (file descriptor 2) a STDOUT (descrittore di file 1).

Nota: l’ordine conta come indicato da liw.fi, 2>&1 1>file.log non funziona.

Curiosamente, questo funziona:

 yourcommand &> filename 

Ma questo dà un errore di syntax:

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

Devi usare:

 yourcommand 1>> filename 2>&1 

Risposta breve: Command >filename 2>&1 o Command &>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 

Notare che l’operatore ‘&’ dice a bash che 2 è un descrittore di file (che punta allo stderr) e non un nome di file. Se avessimo stderror il carattere “&”, questo comando stamperebbe stdout su stdout e creerebbe un file chiamato “2” e scriverà stderror lì.

Sperimentando con il codice qui sopra, puoi vedere esattamente come funzionano gli operatori di reindirizzamento. Ad esempio, cambiando il file quale dei due descrittori 1,2 , viene reindirizzato a /dev/null le seguenti due righe di codice cancellano tutto dallo 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 perché il seguente codice non produce output:

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

Per capirlo veramente, consiglio vivamente di leggere questa pagina web sulle tabelle dei descrittori di file . Supponendo che tu abbia fatto quella lettura, possiamo procedere. Nota che i processi di Bash vanno da sinistra a destra; quindi Bash vede prima >/dev/null (che è lo stesso di 1>/dev/null ), e imposta il descrittore di file 1 in modo che punti a / dev / null invece dello stdout. Fatto questo, Bash si sposta quindi verso 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 puntatori per 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 su / dev / null, e questo è il motivo per cui non viene reso alcun output.


Per verificare se capisci veramente 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 punto del descrittore di file 1, cioè stdout. Quindi imposta il descrittore di file 1 (ricorda che> / dev / null = 1> / dev / null) per puntare a> / dev / null, cancellando così tutto ciò che di solito verrebbe inviato allo standard out. Quindi tutto ciò che ci rimane è quello che non è stato inviato a stdout nella sottotitola (il codice tra parentesi) – cioè “stderror”. La cosa interessante da notare è che anche se 1 è solo un puntatore allo stdout, il reindirizzamento del puntatore 2 a 1 tramite 2>&1 NON forma una catena di puntatori 2 -> 1 -> stdout. Se così fosse, come risultato del reindirizzamento di 1 a / dev / null, il codice 2>&1 >/dev/null darebbe alla catena di puntatori 2 -> 1 -> / dev / null, e quindi il codice non genererebbe nulla, in contrasto con ciò che abbiamo visto sopra.


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

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

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

Key takeaway:

  • I descrittori di file si comportano come puntatori (sebbene i descrittori di file non siano gli stessi di 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 allo stesso punto 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!

Infine dai un’occhiata a queste grandi risorse:

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

 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.

Funziona quasi, ma non da xint;

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

Questa è la soluzione che ho trovato:

 command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile 
  • Primo scambio di stderr e stdout
  • quindi aggiungere lo stdout al file di registro
  • pipe stderr to tee e aggiungilo anche al file di log

Modo “più semplice” (solo bash4): ls * 2>&- 1>&- .

Per tcsh, devo usare il seguente comando:

 command >& file 

Se usi command &> file , darà “Errore nullo invalido” errore.

Le seguenti funzioni possono essere utilizzate per automatizzare il processo di commutazione delle uscite beetwen stdout / stderr e un file di registro.

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