Voglio reindirizzare sia stdout che stderr di un processo su un singolo file. Come posso farlo in Bash?
Voglio reindirizzare sia stdout che stderr di un processo su un singolo file. Come posso farlo in Bash?
Risposte:
Dai un'occhiata qui . Dovrebbe essere:
yourcommand &>filename
(redirect sia stdout
e stderr
al nome del file).
#!/bin/bash
anziché anziché #!/bin/sh
, poiché in richiede bash.
do_something 2>&1 | tee -a some_file
Questo reindirizzerà stderr su stdout e stdout some_file
e lo stamperà su stdout.
do_something &>filename
no. +1.
Ambiguous output redirect.
idea del perché?
$?
non si riferisce più allo stato di uscita di do_something
, ma allo stato di uscita di tee
.
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
# 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.
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
bash your_script.sh 1>file.log 2>&1
1>file.log
indica alla shell di inviare STDOUT al file file.log
e gli 2>&1
dice 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.log
non funziona.
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
&>>
sembra funzionare su BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Risposta breve: Command >filename 2>&1
oCommand &>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 stdout
su stdout e avrebbe creato un file chiamato "2" e scritto stderror
lì.
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/null
seguenti 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/null
primo (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>&1
NON 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:
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
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;
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
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").
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"
@ 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"
In situazioni in cui pensi di usare cose come le exec 2>&1
trovo più facile da leggere se possibile riscrivendo il codice usando funzioni bash come questa:
function myfunc(){
[...]
}
myfunc &>mylog.log