Reindirizza stderr e stdout in Bash
Domanda
Voglio reindirizzare sia stdout che stderr di un processo su un singolo file. Come posso farlo in Bash?
Soluzione
Dai un'occhiata qui . Dovrebbe essere:
yourcommand &>filename
(reindirizza stdout
e stderr
sul nome file).
Altri suggerimenti
do_something 2>&1 | tee -a some_file
Questo reindirizzerà stderr su stdout e stdout su some_file
e stampandolo su stdout.
Puoi reindirizzare stderr su stdout e 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 più popolare & amp; > formato 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).
EDIT: modificato 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, l'eco semplice scriverà in $ 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 di all'interno dello script corrente che influenza tutti i comandi / incorporati (include le forcelle) dopo lo snippet di codice citato.
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 divisione 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 una pipe, una presa o altro.
- Crea FD n. 3 e n. 4 e punta alla stessa "posizione". come # 1 e # 2 rispettivamente. 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 reale terminale STDOUT e STDERR.
- 1 > > (...) reindirizza STDOUT per comandare tra parentesi
- parens (sotto-shell) esegue la lettura 'tee' dallo STDOUT (pipe) di exec e reindirizza al comando 'logger' tramite un'altra pipe per sotto-shell in parentesi. 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 questa:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
... è il seguente:
$ ./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
indica alla shell di inviare STDOUT al file file.log
e 2 > & amp; 1
indica per reindirizzare STDERR (descrittore di file 2) su STDOUT (descrittore di file 1).
Nota: l'ordine è importante come sottolineato da liw.fi, 2 > & amp; 1 1 > file.log
non funziona.
Curiosamente, funziona:
yourcommand &> filename
Ma questo dà un errore di sintassi:
yourcommand &>> filename
syntax error near unexpected token `>'
Devi usare:
yourcommand 1>> filename 2>&1
Risposta breve: Comando > nomefile 2 > & amp; 1
o Comando > > nomefile
Spiegazione:
Considera il seguente codice che stampa la parola "stdout" a stdout e la parola "stderror" a stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Nota che "& amp;" 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 "& amp;", questo comando avrebbe stampato stdout
su stdout e creato un file chiamato " 2 " e scrivi stderror
lì.
Sperimentando il codice sopra, puoi vedere di persona come funzionano gli operatori di reindirizzamento. Ad esempio, cambiando quale file quale dei due descrittori 1,2
, viene reindirizzato a / dev / null
le seguenti due righe di codice eliminano tutto dallo stdout e tutto da rispettivamente stderror (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, ti consiglio vivamente di leggere questo pagina web sulle 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 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 ciò, Bash si sposta a destra e vede 2 > & amp; 1
. In questo modo il descrittore di file 2 punta 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 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 > & amp; 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 a > / 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, reindirizzando il puntatore 2 a 1 tramite 2 > & amp; 1
NON forma una catena di puntatori 2 - > 1 - > stdout. Se lo facesse, a seguito del reindirizzamento di 1 su / dev / null, il codice 2 > & amp; 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 & amp; >
per reindirizzare sia stdout che stderr. Pertanto, per reindirizzare l'output stderr e stdout di qualsiasi comando su \ dev \ null
(che elimina l'output), digitiamo semplicemente
$ command & amp; > / 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)
- Reindirizzamento di un fi
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" )
È relativo: scrivere stdOut & amp; stderr su syslog.
Funziona quasi, ma non da xinted; (
Volevo che una soluzione avesse 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
- Primo scambio stderr e stdout
- quindi aggiungere lo stdout al file di registro
- pipe stderr da inserire e aggiungerlo anche al file di registro
Per situazione, quando " tubazioni " è necessario è possibile utilizzare:
| & amp;
Ad 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 convogliare separatamente STDOUT e STDERR (da STDERR di "sort -c" o da STDERR a "sort -h").
" " più facile; way (solo bash4): ls * 2 > & amp; - 1 > & amp; -
.
Le seguenti funzioni possono essere utilizzate per automatizzare il processo di commutazione degli output tra stdout / stderr e un file di registro. Esempio di utilizzo all'interno dello script:
#!/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"
}
Per tcsh, devo usare il seguente comando:
command >& file
Se usi il comando & amp; > file
, darà " Comando null non valido " di errore.
@ fernando-fabreti
Aggiungendo a quello che hai fatto, ho leggermente modificato le funzioni e rimosso la chiusura di & amp; - 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"
In situazioni in cui pensi di usare cose come exec 2 > & amp; 1
trovo più facile da leggere se possibile riscrivere il codice usando funzioni bash come questa:
function myfunc(){
[...]
}
myfunc &>mylog.log