come produrre testo su schermo e file all'interno di uno script di shell?


49

Attualmente ho uno script di shell che registra i messaggi in un file di registro come questo:

log_file="/some/dir/log_file.log"
echo "some text" >> $log_file
do_some_command
echo "more text" >> $log_file
do_other_command

Quando eseguo questo script, non c'è output sullo schermo e, poiché mi sto collegando al server tramite putty, devo aprire un'altra connessione ed eseguire "tail -f log_file_path.log", perché non riesco a terminare l'esecuzione script e voglio vedere l'output in tempo reale.

Ovviamente, quello che voglio è che i messaggi di testo vengano stampati sullo schermo e nel file, ma mi piacerebbe farlo in una riga, non in due righe, una delle quali non ha reindirizzamento al file.

Come raggiungere questo obiettivo?

Risposte:


71

Questo funziona:

command | tee -a "$log_file"

teesalva l'input in un file (usa -aper aggiungere anziché sovrascrivere) e copia anche l'input nell'output standard.


8

Puoi usare un here-doc e. procuratelo per un modello di collettore generale efficiente e compatibile con POSIX.

. 8<<-\IOHERE /proc/self/fd/8

command
 
fn() { declaration ; } <<WHATEVER
# though a nested heredoc might be finicky
# about stdin depending on shell
WHATEVER
cat -u ./stdout | ./works.as >> expect.ed
IOHERE

Quando apri l'ereditarietà, segnali alla shell con un token di input IOHERE che dovrebbe reindirizzare il suo input al descrittore di file specificato fino a quando non incontra l'altra estremità del token limiter. Mi sono guardato intorno ma non ho visto molti esempi dell'uso del numero fd di reindirizzamento, come ho mostrato sopra in combinazione con l'operatore heredoc, sebbene il suo utilizzo sia chiaramente specificato nelle linee guida di comando shell POSIX di base. La maggior parte delle persone punta semplicemente su stdin e spara, ma trovo che gli scriptlet di sourcing in questo modo possano mantenere lo stdin libero e le app costituenti da lamentarsi dei percorsi I / o bloccati.

Il contenuto dell'eredoc viene trasmesso in streaming al descrittore di file specificato, che a sua volta viene quindi interpretato come codice shell ed eseguito da. incorporato, ma non senza specificare un percorso specifico per. . Se il percorso / proc / self ti dà problemi, prova / dev / fd / n o / proc / $$. Questo stesso metodo funziona sui tubi, a proposito:

cat ./*.sh | . /dev/stdin

È probabilmente meno saggio di quanto sembri. Puoi fare lo stesso con sh, ovviamente, ma lo scopo di. È quello di eseguire nell'attuale ambiente shell, che è probabilmente quello che vuoi e, a seconda della tua shell, è molto più probabile che lavori con un heredoc che con una pipe anonima standard.

Comunque, come probabilmente avrai notato, non ho ancora risposto alla tua domanda. Ma se ci pensi, allo stesso modo in cui l'ereditarietà trasmette tutto il tuo codice a.

. 5<<EOIN /dev/fd/5 |\ 
    tee -a ./log.file | cat -u >$(tty)
script
 
more script
EOIN

Quindi tutto il terminale stdout da qualsiasi codice eseguito nella tua eredità viene reindirizzato. ovviamente e può essere facilmente staccato da una singola pipa. Ho incluso la chiamata cat senza buffer perché non sono chiaro sull'attuale direzione stdout, ma è probabilmente ridondante (quasi sicuramente è come scritto comunque) e la pipeline può probabilmente terminare proprio a T.

Potresti anche mettere in dubbio la citazione di barra rovesciata mancante nel secondo esempio. Questa parte è importante da capire prima di saltare e potrebbe darti alcune idee su come può essere utilizzata. Un limitatore ereditario citato (finora abbiamo usato IOHERE ed EOIN, e il primo che ho citato con una barra rovesciata, sebbene le virgolette "singole" o "doppie" avrebbero lo stesso scopo) impediranno alla shell di eseguire qualsiasi espansione di parametro sul contenuti, ma un limitatore non quotato lascerà i suoi contenuti aperti all'espansione. Le conseguenze di ciò quando la tua eredità è. provenienti sono drammatici:

. 3<<HD ${fdpath}/3
: \${vars=$(printf '${var%s=$((%s*2))},' `seq 1 100`)} 
HD
echo $vars
> 4,8,12 
echo $var1 $var51
> 4 104

Poiché non ho citato il limitatore ereditario, la shell ha espanso il contenuto mentre lo leggeva e prima di servire il descrittore di file risultante. eseguire. Ciò essenzialmente ha comportato l'analisi dei comandi due volte, comunque quelli espandibili. Poiché la barra rovesciata ha citato l'espansione del parametro $ vars, la shell ha ignorato la sua dichiarazione al primo passaggio e ha rimosso la barra rovesciata in modo che l'intero contenuto espanso della stampa potesse essere valutato da null quando. proveniente dallo script al secondo passaggio.

Questa funzionalità è fondamentalmente esattamente ciò che la pericolosa shell eval incorporata può fornire, anche se la citazione è molto più facile da gestire in una eredità che con eval, e può essere altrettanto pericolosa. A meno che non lo pianifichi attentamente, probabilmente è meglio citare il limitatore "EOF" come una questione di abitudine. Sto solo dicendo.

EDIT: Eh, sto guardando indietro a questo e pensando che sia un po 'troppo lungo. Se TUTTO ciò che devi fare è concatenare più output in una pipe, il metodo più semplice è usare solo un:

{ or ( command ) list ; } | tee -a ea.sy >>pea.sy

I ricci cercheranno di eseguire i contenuti nella shell corrente mentre i genitori verranno automaticamente sottoposti a sub-out. Tuttavia, chiunque può dirtelo e, almeno secondo me, il. La soluzione ereditaria è un'informazione molto più preziosa, specialmente se vuoi capire come funziona realmente la shell.

Divertiti!


3

Ho trovato questa risposta mentre cercavo di modificare uno script di installazione per scrivere un registro di installazione.

La mia sceneggiatura è già piena di dichiarazioni di eco come:

echo "Found OLD run script $oldrunscriptname"
echo "Please run OLD tmunsetup script first!"

E non volevo che un'istruzione tee la eseguisse (o un altro script per chiamare quella esistente con una tee), quindi ho scritto questo:

#!/bin/bash
# A Shell subroutine to echo to screen and a log file

LOGFILE="junklog"

echolog()
(
echo $1
echo $1 >> $LOGFILE
)


echo "Going"
echolog "tojunk"

# eof

Quindi ora nel mio script originale, posso semplicemente cambiare 'echo' in 'echolog' dove voglio l'output in un file di registro.

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.