Come reindirizzare l'output di un intero script shell all'interno dello script stesso?


205

È possibile reindirizzare tutto l'output di uno script della shell Bourne da qualche parte, ma con comandi shell all'interno dello script stesso?

Reindirizzare l'output di un singolo comando è semplice, ma voglio qualcosa di più simile a questo:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Significato: se lo script viene eseguito in modo non interattivo (ad esempio cron), salva l'output di tutto in un file. Se eseguito in modo interattivo da una shell, lasciare l'output su stdout come al solito.

Voglio farlo per uno script normalmente eseguito dall'utilità periodica di FreeBSD. Fa parte della corsa quotidiana, che normalmente non mi interessa vedere ogni giorno via e-mail, quindi non l'ho inviato. Tuttavia, se qualcosa all'interno di questo particolare script fallisce, questo è importante per me e mi piacerebbe essere in grado di catturare e inviare via e-mail l'output di questa parte dei lavori quotidiani.

Aggiornamento: la risposta di Joshua è precisa, ma volevo anche salvare e ripristinare stdout e stderr nell'intero script, che è fatto in questo modo:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4

2
Il test per $ TERM non è il modo migliore per testare la modalità interattiva. Invece, verifica se stdin è un tty (test -t 0).
Chris Jester-Young,

2
In altre parole: se [! -t 0]; quindi exec> somefile 2> & 1; fi
Chris Jester-Young,

1
Vedi qui per tutta la bontà: http://tldp.org/LDP/abs/html/io-redirection.html Fondamentalmente ciò che è stato detto da Giosuè. Il file exec> reindirizza stdout a un file specifico, exec <il file sostituisce stdin con il file, ecc. È lo stesso del solito ma usando exec (vedi man exec per maggiori dettagli).
Loki,

Nella sezione di aggiornamento, dovresti anche chiudere gli FD 3 e 4, in questo modo: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh,

Risposte:


173

Affrontare la domanda come aggiornata.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Le parentesi graffe "{...}" forniscono un'unità di reindirizzamento I / O. Le parentesi graffe devono apparire dove potrebbe apparire un comando - semplicisticamente, all'inizio di una linea o dopo un punto e virgola. ( Sì, questo può essere reso più preciso; se vuoi cavillare, fammelo sapere. )

Hai ragione, puoi preservare lo stdout e lo stderr originali con i reindirizzamenti che hai mostrato, ma di solito è più semplice per le persone che devono mantenere lo script in seguito per capire cosa sta succedendo se cerchi il codice reindirizzato come mostrato sopra.

Le sezioni pertinenti del manuale di Bash sono Raggruppamento Comandi e I / O reindirizzamento . Le sezioni pertinenti specifiche POSIX shell sono comandi composti e I / O reindirizzamento . Bash ha alcune notazioni extra, ma è comunque simile alle specifiche della shell POSIX.


3
Questo è molto più chiaro del salvataggio dei descrittori originali e del loro ripristino in un secondo momento.
Steve Madsen,

23
Ho dovuto fare un po 'di ricerche su google per capire cosa sta realmente facendo, quindi volevo condividere. Le parentesi graffe diventano un "blocco di codice" che, in effetti, crea una funzione anonima . L'output di tutto nel blocco di codice può quindi essere reindirizzato (vedi Esempio 3-2 da quel link). Si noti inoltre che le parentesi graffe non avviano una subshell , ma reindirizzamenti I / O simili possono essere eseguiti con subshells usando le parentesi.
chris,

3
Mi piace questa soluzione meglio delle altre. Anche una persona con solo la comprensione di base del reindirizzamento I / O può capire cosa sta succedendo. Inoltre, è più dettagliato. E, come Pythoner, adoro il verboso.
John Red,

Meglio fare >>. Alcune persone hanno l'abitudine di >. L'aggiunta è sempre più sicura e più consigliata della sovratensione ***. Qualcuno ha scritto un'applicazione che utilizza il comando di copia standard per esportare alcuni dati nella stessa destinazione.
neverMind9,

Quell'app è ccc.bmw71 (.pro) (Widget Monitor batteria 3C, precedentemente "Widget Monitor batteria") esporta sempre la cronologia della batteria in /sdcard/bmw_history.txt. Se esiste già, indovina un po ', OVERWRITE! Questo mi ha fatto perdere accidentalmente un po 'di storia della batteria. Ho impostato il limite in giorni da 30 a un numero molto alto, che lo ha invalidato e rimesso a 30. Volevo esportare la cronologia corrente della batteria prima di importare il backup esistente. Poi è successo.
neverMind9,

175

Tipicamente posizioneremmo uno di questi nella parte superiore o vicino alla sceneggiatura. Gli script che analizzano le loro righe di comando farebbero il reindirizzamento dopo l'analisi.

Invia stdout a un file

exec > file

con stderr

exec > file                                                                      
exec 2>&1

aggiunge sia stdout che stderr al file

exec >> file
exec 2>&1

Come Jonathan Leffler ha menzionato nel suo commento :

execha due lavori separati. Il primo è sostituire la shell (script) attualmente in esecuzione con un nuovo programma. L'altro sta cambiando i reindirizzamenti I / O nella shell corrente. Questo si distingue per non avere argomenti a exec.


7
Dico anche aggiungere 2> & 1 alla fine di questo, solo così anche Stderr viene catturato. :-)
Chris Jester-Young,

7
Dove li metti? Nella parte superiore della sceneggiatura?
colan,

4
Con questa soluzione è necessario anche reimpostare il reindirizzamento che esce dallo script. La prossima risposta di Jonathan Leffler è più "a prova di fallimento" in questo senso.
Chuim,

6
@JohnRed: execha due lavori separati. Uno consiste nel sostituire lo script corrente con un altro comando, usando lo stesso processo: si specifica l'altro comando come argomento per exec(e si possono modificare i reindirizzamenti I / O mentre lo si fa). L'altro lavoro sta modificando i reindirizzamenti I / O nello script di shell corrente senza sostituirlo. Questa notazione si distingue per non avere un comando come argomento per exec. La notazione in questa risposta è della variante "Solo I / O": cambia solo il reindirizzamento e non sostituisce lo script in esecuzione. (Il setcomando è similmente multiuso).
Jonathan Leffler,

18
exec > >(tee -a "logs/logdata.log") 2>&1stampa i registri sullo schermo e li scrive in un file
shriyog

32

Puoi rendere l'intero script una funzione come questa:

main_function() {
  do_things_here
}

quindi alla fine dello script hai questo:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

In alternativa, è possibile registrare tutto nel file di registro ogni esecuzione e comunque inviarlo a stdout semplicemente facendo:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log

Intendevi main_function >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez

Mi piace usare main_function in tale pipe. Ma in questo caso il tuo script non restituisce il valore di ritorno originale. Nel caso bash dovresti uscire quindi usando 'exit $ {PIPESTATUS [0]}'.
rudimeier,

7

Per salvare lo stdout e lo stderr originali è possibile utilizzare:

exec [fd number]<&1 
exec [fd number]<&2

Ad esempio, il codice seguente stamperà "walla1" e "walla2" nel file di registro ( a.txt), "walla3" in stdout, "walla4" in stderr.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6

1
Normalmente, sarebbe meglio usare exec 5>&1e exec 6>&2, usando la notazione di reindirizzamento dell'output piuttosto che la notazione di reindirizzamento dell'output per gli output. Se ne va via perché quando lo script viene eseguito da un terminale, anche l'input standard è scrivibile e sia l'output standard che l'errore standard sono leggibili in virtù (o è 'vice'?) Di una stranezza storica: il terminale è aperto per lettura e scrittura e la stessa descrizione del file aperto viene utilizzata per tutti e tre i descrittori di file I / O standard.
Jonathan Leffler,

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.