Scrittura degli output su file di log e console


98

Nella shell di Unix, ho un file env (il file env definisce i parametri necessari per eseguire lo script utente come nome e percorso del file di registro, reindirizzare gli output e gli errori nel file di registro, i dettagli di connessione al database, ecc. ) Che reindirizza tutti gli output ( messaggi di eco ) e gli errori nel file di registro dallo script eseguito utilizzando il codice seguente:

exec 1>>${LOG_FILE}
exec 2>>${LOG_FILE}

Il file env viene eseguito all'inizio di ogni script. A causa del codice sopra riportato nel file env, tutti gli output della console che potrebbero essere output dell'utente o errori vengono direttamente inviati al file di registro che è ciò di cui avevo effettivamente bisogno.

Ma ci sono alcuni output utente selettivi che desidero vengano visualizzati sia nella console che nel file di registro. Ma a causa del codice sopra non sono in grado di farlo.

So che se rimuovo il codice sopra posso ottenere il risultato desiderato per questo caso, ma dovrò scrivere manualmente tutti gli altri output nel file di registro, il che non è un compito facile.

C'è un modo per ottenere l'output sia nella console che nel file di registro senza rimuovere i codici sopra?

Risposte:


105
exec 3>&1 1>>${LOG_FILE} 2>&1

invierà l'output di stdout e stderr nel file di registro, ma lascerà anche fd 3 connesso alla console, quindi puoi farlo

echo "Some console message" 1>&3

per scrivere un messaggio solo sulla console, o

echo "Some console and log file message" | tee /dev/fd/3

per scrivere un messaggio sia sulla console che sul file di log - teeinvia il suo output sia al proprio fd 1 (che qui è il LOG_FILE) sia al file in cui gli hai detto di scrivere (che qui è fd 3, cioè la console).

Esempio:

exec 3>&1 1>>${LOG_FILE} 2>&1

echo "This is stdout"
echo "This is stderr" 1>&2
echo "This is the console (fd 3)" 1>&3
echo "This is both the log and the console" | tee /dev/fd/3

stamperebbe

This is the console (fd 3)
This is both the log and the console

sulla console e metti

This is stdout
This is stderr
This is both the log and the console

nel file di registro.


2
Ha funzionato proprio come hai suggerito. Ma non ho capito tee / dev / fd / 3 . So che tee scrive un messaggio nel file di log e nella console, ma non ho capito esattamente / dev / fd / 3 usato dopo tee
abinash shrestha

@shrestha è un uso insolito del tee, sono d'accordo. Il /dev/fd/3è un nome di file che si riferisce a "fd aperto 3", così tee /dev/fd/3scriverà tutto ciò arriva sul suo stdin per fd 1 e anche fd 3 (il /dev/fd/3file). Fd 1 è connesso al file di log, fd 3 è connesso alla console.
Ian Roberts

Inoltre, vuoi solo scrivere su un file e non la console prova: echo "blah" | tee file1.txt | tee file2.txt >/dev/null "Blah" non verrà messo in file1.txt e file2.txt, ma non scritto nella console.
pericolo89

Questo mi è stato molto utile. Anche se ho notato qualcosa che potrebbe essere fuori tema, ma forse alcuni di voi ne conoscono le ragioni. Sto eseguendo script R da uno script bash. L'output della console di varie funzioni R è colorato (come l'ho definito). quando si utilizza l'approccio di cui sopra per reindirizzare l'output alla console E al file di log, l'output della console non è più colorato. Quale potrebbe essere la ragione?
dieHellste

1
@dieHellste alcuni programmi sono in grado di rilevare quando il loro output viene reindirizzato a un altro processo (in questo caso tee, che a sua volta scrive sul terminale) piuttosto che andare direttamente a un terminale e regolare il loro output in modo che corrisponda.
Ian Roberts

43

Sì, vuoi usare tee:

tee - legge dallo standard input e scrive sullo standard output e sui file

Invia semplicemente il tuo comando a tee e passa il file come argomento, in questo modo:

exec 1 | tee ${LOG_FILE}
exec 2 | tee ${LOG_FILE}

Ciò stampa l'output su STDOUT e scrive lo stesso output in un file di registro. Vedere man teeper ulteriori informazioni.

Nota che questo non scriverà stderr nel file di log, quindi se vuoi combinare i due flussi, usa:

exec 1 2>&1 | tee ${LOG_FILE}

2
La soluzione di cui sopra non ha funzionato. Ho il file redirect.env come: ##### redirect.env ###### export LOG_FILE = log.txt exec 1 2> & 1 | tee -a $ {LOG_FILE} exec 1 | tee -a $ {LOG_FILE} exec 2 | tee -a $ {LOG_FILE} ######### e il file di lavoro conteneva il seguente codice: ##### output.sh ##### #! / bin / sh. redirect.env echo "Valid output" ech "invalid output" ############## ma ottengo l'errore: #### redirect.env: riga 3: exec: 1: non trovato redirect.env: riga 5: exec: 1: non trovato redirect.env: riga 6: exec: 2: non trovato #### e nel file di registro ottengo lo stesso errore. Sto facendo qualcosa di sbagliato?
abinash shrestha

È molto difficile da dire, poiché le nuove righe sono eliminate nel tuo commento. Potresti aggiungere una pasta del codice da qualche parte come il gist ?
Jon Cairns,

1
Ho aggiunto i file al collegamento UnixRedirect . I file correlati sono redirect.env e output.sh
abinash shrestha

1
Questo codice sembra non funzionare (almeno non più). Di solito riceviscript.sh: line 5: exec: 1: not found
tftd

39

Ho provato la risposta di joonty, ma ho anche ottenuto il

exec: 1: non trovato

errore. Questo è ciò che funziona meglio per me ( confermato per funzionare anche in zsh):

#!/bin/bash
LOG_FILE=/tmp/both.log
exec > >(tee ${LOG_FILE}) 2>&1
echo "this is stdout"
chmmm 77 /makeError

Il file /tmp/both.log in seguito contiene

this is stdout
chmmm command not found 

Il file /tmp/both.log viene aggiunto a meno che non si rimuova -a dal tee.

Suggerimento: >(...)è una sostituzione del processo. Consente di execeseguire il teecomando come se fosse un file.


1
Questo ha funzionato come un fascino per me, mentre le altre risposte sono state sbagliate.
Jay Taylor

Grazie per aver condiviso questo snippet. Questo sembra funzionare bene (rispetto alle altre risposte)!
tftd

Funziona, ma ora la mia shell è diversa dopo l'esecuzione.
Josh Usre

@JoshUsre Se vuoi aiuto o vuoi aiutare altri utenti SO, per favore spiega cosa c'è di diverso e descrivi la tua shell, versione, sistema operativo ecc.
alfonx

1
Se non è necessario distinguere tra stdout e stderr, è possibile combinare le istruzioni in exec > >(tee ${LOG_FILE}) 2>&1.
Darkdragon

5

Volevo visualizzare i log su stdout e il file di log insieme al timestamp. Nessuna delle risposte precedenti ha funzionato per me. Ho utilizzato la sostituzione del processo e il comando exec e ho creato il codice seguente. Registri di esempio:

2017-06-21 11:16:41+05:30 Fetching information about files in the directory...

Aggiungi le seguenti righe all'inizio del tuo script:

LOG_FILE=script.log
exec > >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done)
exec 2> >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done >&2)

Spero che questo aiuti qualcuno!


È possibile registrare tutti gli errori dell'applicazione per accedere alla directory host in modo che possa aiutare devops ....
Prasad Shinde

3

per il file di registro è possibile inserire la data nei dati di testo. il codice seguente può aiutare

# declaring variables

Logfile="logfile.txt"   
MAIL_LOG="Message to print in log file"  
Location="were is u want to store log file"

cd $Location   
if [ -f $Logfile ]  
then   
echo "$MAIL_LOG " >> $Logfile

else        

touch $Logfile   
echo "$MAIL_LOG" >> $Logfile    

fi  

ouput: 2. Il file di registro verrà creato alla prima esecuzione e continuerà ad essere aggiornato dalle esecuzioni successive. Nel caso in cui il file di registro manchi in una futura esecuzione, lo script creerà un nuovo file di registro.


1

Ho trovato un modo per ottenere l'output desiderato. Anche se potrebbe essere un modo un po 'non ortodosso. Comunque eccolo qui. Nel file redir.env ho il seguente codice:

#####redir.env#####    
export LOG_FILE=log.txt

      exec 2>>${LOG_FILE}

    function log {
     echo "$1">>${LOG_FILE}
    }

    function message {
     echo "$1"
     echo "$1">>${LOG_FILE}
    }

Quindi nello script effettivo ho i seguenti codici:

#!/bin/sh 
. redir.env
echo "Echoed to console only"
log "Written to log file only"
message "To console and log"
echo "This is stderr. Written to log file only" 1>&2

Qui echo restituisce solo alla console, registra gli output solo al file di registro e gli output dei messaggi sia al file di registro che alla console.

Dopo aver eseguito il file di script sopra, ho i seguenti output:

In console

In console
Echoed to console only
To console and log

Per il file di registro

Nel file di registro Scritto solo nel file di registro
Questo è stderr. Scritto solo nel file di registro
Per console e log

Spero che questo aiuto.


1

Prova questo, farà il lavoro:

log_file=$curr_dir/log_file.txt
exec > >(tee -a ${log_file} )
exec 2> >(tee -a ${log_file} >&2)

Questo exec > >(tee -a ${log_file} )funziona perfetto per le mie esigenze. Le soluzioni precedenti sopra avrebbero interrotto parti del mio script che forzavano l'uscita se fallivano. Grazie
MitchellK

1
    #
    #------------------------------------------------------------------------------
    # echo pass params and print them to a log file and terminal
    # with timestamp and $host_name and $0 PID
    # usage:
    # doLog "INFO some info message"
    # doLog "DEBUG some debug message"
    # doLog "WARN some warning message"
    # doLog "ERROR some really ERROR message"
    # doLog "FATAL some really fatal message"
    #------------------------------------------------------------------------------
    doLog(){
        type_of_msg=$(echo $*|cut -d" " -f1)
        msg=$(echo "$*"|cut -d" " -f2-)
        [[ $type_of_msg == DEBUG ]] && [[ $do_print_debug_msgs -ne 1 ]] && return
        [[ $type_of_msg == INFO ]] && type_of_msg="INFO " # one space for aligning
        [[ $type_of_msg == WARN ]] && type_of_msg="WARN " # as well

        # print to the terminal if we have one
        test -t 1 && echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg"

        # define default log file none specified in cnf file
        test -z $log_file && \
            mkdir -p $product_instance_dir/dat/log/bash && \
                log_file="$product_instance_dir/dat/log/bash/$run_unit.`date "+%Y%m"`.log"
        echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg" >> $log_file
    }
    #eof func doLog

0

Trovo molto utile aggiungere sia stdout che stderr a un file di registro. Sono stato contento di vedere una soluzione di alfonx con exec > >(tee -a), perché mi chiedevo come ottenere questo risultato usando exec. Mi sono imbattuto in una soluzione creativa utilizzando la sintassi here-doc e .: /unix/80707/how-to-output-text-to-both-screen-and-file-inside-a-shell -script

Ho scoperto che in zsh, la soluzione here-doc può essere modificata utilizzando il costrutto "multios" per copiare l'output sia su stdout / stderr che sul file di log:

#!/bin/zsh
LOG=$0.log
# 8 is an arbitrary number;
# multiple redirects for the same file descriptor 
# triggers "multios"
. 8<<\EOF /dev/fd/8 2>&2 >&1 2>>$LOG >>$LOG
# some commands
date >&2
set -x
echo hi
echo bye
EOF
echo not logged

Non è leggibile come la execsoluzione, ma ha il vantaggio di consentire di registrare solo una parte dello script. Ovviamente, se ometti l'EOF, l'intero script viene eseguito con la registrazione. Non sono sicuro di come zshimplementi multios, ma potrebbe avere meno overhead di tee. Purtroppo sembra che non si possano usare multios con exec.

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.