reindirizzare COPY di stdout per registrare il file dallo script bash stesso


235

So come reindirizzare stdout su un file:

exec > foo.log
echo test

questo metterà il 'test' nel file foo.log.

Ora voglio reindirizzare l'output nel file di registro E tenerlo su stdout

cioè può essere fatto in modo banale dall'esterno dello script:

script | tee foo.log

ma voglio dichiararlo all'interno dello script stesso

Provai

exec | tee foo.log

ma non ha funzionato.


3
La tua domanda è formulata male. Quando invochi 'exec> foo.log', lo stdout dello script è il file foo.log. Penso che tu voglia dire che vuoi che l'output vada su foo.log e su tty, dato che andare su foo.log sta andando su stdout.
William Pursell,

quello che mi piacerebbe fare è usare il | su "exec". sarebbe perfetto per me, ad esempio "exec | tee foo.log", sfortunatamente non puoi usare il reindirizzamento della pipe sulla chiamata exec
Vitaly Kushner

Risposte:


297
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

Si noti che bashnon lo è sh. Se invochi lo script con sh myscript.sh, visualizzerai un errore simile asyntax error near unexpected token '>' .

Se si lavora con trap di segnale, è possibile utilizzare l' tee -iopzione per evitare l'interruzione dell'uscita in caso di segnale. (Grazie a JamesThomasMoon1979 per il commento.)


Strumenti che cambiano il loro output a seconda che scrivano su una pipe o su un terminale (ls usando i colori e l'output in colonne, per esempio) rileveranno il costrutto di cui sopra nel senso che hanno output su una pipe.

Ci sono opzioni per applicare il colorizing / columnizing (ad es ls -C --color=always.). Notare che ciò comporterà anche la scrittura dei codici colore nel file di registro, rendendolo meno leggibile.


5
Tee sulla maggior parte dei sistemi è bufferizzato, quindi l'output potrebbe non arrivare fino al termine dello script. Inoltre, poiché questa T è in esecuzione in una subshell, non in un processo figlio, wait non può essere utilizzato per sincronizzare l'output con il processo chiamante. Quello che vuoi è una versione senza buffer di tee simile a bogomips.org/rainbows.git/commit/…

14
@ Barry: POSIX specifica che teenon dovrebbe bufferizzare il suo output. Se fa buffer sulla maggior parte dei sistemi, è rotto sulla maggior parte dei sistemi. Questo è un problema delle teeimplementazioni, non della mia soluzione.
DevSolar,

3
@Sebastian: execè molto potente, ma anche molto coinvolto. È possibile "eseguire il backup" dello stdout corrente su un altro descrittore di file, quindi ripristinarlo in seguito. "Tutorial su bash exec" di Google, ci sono molte cose avanzate là fuori.
DevSolar

2
@AdamSpiers: Non sono nemmeno sicuro di cosa trattasse Barry. Bash's execè documentato per non avviare nuovi processi, >(tee ...)è una sostituzione standard denominata pipe / process e &il reindirizzamento ovviamente non ha nulla a che fare con lo sfondo ...? :-)
DevSolar

11
Suggerisco di passare -ia tee. Altrimenti, gli interrupt di segnale (trap) interromperanno lo stdout nello script principale. Ad esempio, se hai un trap 'echo foo' EXITe poi premi ctrl+c, non vedrai " pippo ". Quindi vorrei modificare la risposta a exec &> >(tee -ia file).
JamesThomasMoon1979,

174

La risposta accettata non conserva STDERR come descrittore di file separato. Questo significa

./script.sh >/dev/null

non verrà inviato baral terminale, ma solo al file di log e

./script.sh 2>/dev/null

produrrà entrambi fooe baral terminale. Chiaramente non è questo il comportamento che un normale utente si aspetta. Questo può essere risolto utilizzando due processi a T separati, entrambi aggiunti allo stesso file di registro:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(Notare che quanto sopra inizialmente non tronca il file di registro; se si desidera tale comportamento, è necessario aggiungere

>foo.log

all'inizio della sceneggiatura.)

La specifica POSIX.1-2008tee(1) richiede che l'output sia senza buffer, cioè nemmeno con buffer di linea, quindi in questo caso è possibile che STDOUT e STDERR possano finire sulla stessa riga di foo.log; tuttavia ciò potrebbe accadere anche sul terminale, quindi il file di registro sarà un riflesso fedele di ciò che potrebbe essere visto sul terminale, se non un suo esatto mirror. Se si desidera che le linee STDOUT siano separate in modo pulito dalle linee STDERR, prendere in considerazione l'utilizzo di due file di registro, possibilmente con prefissi di data e data su ciascuna riga per consentire il riassemblaggio cronologico in seguito.


Per qualche ragione, nel mio caso, quando lo script viene eseguito da una chiamata di sistema c (program), i due processi secondari a T continuano ad esistere anche dopo la chiusura dello script principale. Quindi ho dovuto aggiungere trappole come questa:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
alveko,

15
Suggerisco di passare -ia tee. Altrimenti, gli interrupt di segnale (trap) interromperanno lo stdout nello script. Ad esempio, se tu trap 'echo foo' EXITe poi premi ctrl+c, non vedrai " pippo ". Quindi vorrei modificare la risposta a exec > >(tee -ia foo.log).
JamesThomasMoon1979,

Ho fatto alcuni piccoli script "reperibili" basati su questo. Possono usarli in uno script come . logo . log foo.log: sam.nipl.net/sh/log sam.nipl.net/sh/log-a
Sam Watkins

1
Il problema con questo metodo è che i messaggi verranno STDOUTvisualizzati prima come batch, quindi verranno STDERRvisualizzati i messaggi . Non sono interfogliati come di solito previsto.
CMCDragonkai,

28

Soluzione per conchiglie busybox, macOS bash e non bash

La risposta accettata è sicuramente la scelta migliore per bash. Sto lavorando in un ambiente Busybox senza accesso a bash, e non capiscoexec > >(tee log.txt) sintassi. Inoltre non lo faexec >$PIPE correttamente, cercando di creare un file ordinario con lo stesso nome della pipe denominata, che non riesce e si blocca.

Spero che questo sarebbe utile a qualcun altro che non ha la bash.

Inoltre, per chiunque utilizzi una pipe denominata, è sicuro rm $PIPE , perché scollega la pipe dal VFS, ma i processi che la utilizzano mantengono comunque un conteggio di riferimento su di essa fino al termine.

Nota che l'uso di $ * non è necessariamente sicuro.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here

Questa è l'unica soluzione che ho visto finora che funziona su Mac
Mike Baglio Jr.

19

All'interno del file di script, inserisci tutti i comandi tra parentesi, in questo modo:

(
echo start
ls -l
echo end
) | tee foo.log

5
pedanticamente, potrebbe anche usare parentesi graffe ( {})
glenn jackman

beh sì, l'ho considerato, ma questo non è il reindirizzamento dell'attuale shell stdout, il suo tipo di cheat, in realtà esegui una subshell e fai un reindirizzamento del piper normale su di esso. funziona pensato. Sono diviso con questo e la soluzione "tail -f foo.log &". aspetterò un po 'per vedere se può essere una superficie migliore. se non probabilmente si stabilirà;)
Vitaly Kushner

8
{} esegue un elenco nell'attuale ambiente shell. () esegue un elenco in un ambiente subshell.

Dannazione. Grazie. La risposta accettata lassù non ha funzionato per me, cercando di pianificare uno script da eseguire in MingW su un sistema Windows. Mi sono lamentato, credo, della sostituzione del processo non implementata. Questa risposta ha funzionato bene, dopo la seguente modifica, per catturare sia stderr che stdout: `` `-) | tee foo.log +) 2> & 1 | tee foo.log
Jon Carter,

14

Modo semplice per creare un log di script bash su syslog. L'output dello script è disponibile sia tramite/var/log/syslog che attraverso stderr. syslog aggiungerà utili metadati, inclusi i timestamp.

Aggiungi questa riga in alto:

exec &> >(logger -t myscript -s)

In alternativa, invia il registro in un file separato:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Ciò richiede moreutils(per il tscomando, che aggiunge i timestamp).


10

Usando la risposta accettata il mio script continuava a tornare eccezionalmente in anticipo (subito dopo 'exec>> (tee ...)') lasciando il resto del mio script in esecuzione in background. Dato che non riuscivo a far funzionare quella soluzione a modo mio, ho trovato un'altra soluzione / aggirare il problema:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

Questo fa in modo che l'output dello script passi dal processo, attraverso la pipe al processo in sottofondo di 'tee' che registra tutto su disco e sullo stdout originale dello script.

Nota che 'exec &>' reindirizza sia stdout che stderr, potremmo reindirizzarli separatamente se vogliamo, o cambiare in 'exec>' se vogliamo solo stdout.

Anche se la pipe viene rimossa dal file system all'inizio dello script continuerà a funzionare fino al termine dei processi. Non possiamo fare riferimento ad esso usando il nome del file dopo la rm-line.


Risposta simile come seconda idea da David Z . Dai un'occhiata ai suoi commenti. +1 ;-)
olibre

Funziona bene. Non capisco la $logfileparte di tee < ${logfile}.pipe $logfile &. In particolare, ho provato a modificare questo per catturare le righe di registro dei comandi (da set -x) a file completamente espanse mentre mostravo solo le linee senza portare '+' in stdout passando a (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &ma ho ricevuto un messaggio di errore $logfile. Puoi spiegare la teelinea in modo un po 'più dettagliato?
Chris Johnson,

L'ho provato e sembra che questa risposta non conservi STDERR (è unita a STDOUT), quindi se fai affidamento sul fatto che i flussi siano separati per il rilevamento degli errori o altri reindirizzamenti, dovresti guardare la risposta di Adam.
HeroCC


1

Non posso dire di essere a mio agio con nessuna delle soluzioni basate su exec. Preferisco usare tee direttamente, quindi faccio lo script stesso con tee quando richiesto:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

Questo ti permette di fare questo:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

Puoi personalizzarlo, ad esempio imposta invece tee = false come predefinito, fai invece che TEE mantenga il file di registro, ecc. Immagino che questa soluzione sia simile a quella di jbarlow, ma più semplice, forse la mia ha dei limiti che non ho ancora incontrato.


-1

Nessuna di queste è una soluzione perfetta, ma qui ci sono un paio di cose che potresti provare:

exec >foo.log
tail -f foo.log &
# rest of your script

o

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

Il secondo lascerebbe in piedi un file di pipe se qualcosa dovesse andare storto con il tuo script, il che potrebbe essere o meno un problema (cioè forse potresti rmfarlo nella shell genitore in seguito).


1
tail lascerà indietro un processo in esecuzione nel secondo script tee verrà bloccato, oppure dovrai eseguirlo con & nel qual caso lascerà il processo come nel 1 °.
Vitaly Kushner,

@Vitaly: oops, teeho dimenticato lo sfondo - l'ho modificato. Come ho detto, nessuna delle due è una soluzione perfetta, ma i processi in background verranno interrotti quando termina la shell genitore, quindi non devi preoccuparti per loro delle risorse di hogging per sempre.
David Z,

1
Yikes: questi sembrano attraenti, ma anche l'output di tail -f sta per foo.log. Puoi risolverlo eseguendo tail -f prima del exec, ma la coda rimane ancora in esecuzione dopo il termine del genitore. Devi ucciderlo esplicitamente, probabilmente in una trappola 0.
William Pursell il

Yeap. Se lo script è in background, lascia i processi dappertutto.
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.