Mostra solo stderr sullo schermo ma scrivi sia stdout che stderr su file


24

Come posso usare la magia BASH per raggiungere questo obiettivo?
Voglio vedere solo l'output di stderr sullo schermo,
ma voglio che sia stdout che stderr siano scritti su un file.

Chiarimento: voglio che sia stdout che stderr finiscano nello stesso file. Nell'ordine in cui accadono.
Sfortunatamente nessuna delle risposte sotto lo fa.


Risposte:


14

Anche senza alcun reindirizzamento, o con nient'altro che >logfile 2>&1, non sei sicuro di vedere l'output in ordine di generazione.

Per i principianti, lo stdout dall'applicazione sarà bufferizzato in linea (a tty) o bufferizzato (a una pipeline) ma stderr è senza buffer, quindi le relazioni tra l'ordine di output sono interrotte per quanto riguarda un lettore. Le fasi successive in qualsiasi pipeline che potresti creare non otterranno l'accesso ordinato in modo deterministico ai due flussi (sono concettualmente cose che accadono in parallelo e sei sempre soggetto allo scheduler - se prima che il tuo lettore ottenga una fetta lo scrittore ha già scritto su entrambe le pipe, non si può dire quale sia venuto per primo).

"[T] ordina che accadano" è noto solo all'applicazione. Ordinare l'output su stdout / stderr è un problema ben noto - classico, forse -.


7

Quando usi la costruzione: 1>stdout.log 2>&1sia stderr che stdout vengono reindirizzati al file perché il reindirizzamento stdout è impostato prima del reindirizzamento stderr .

Se si inverte l'ordine, è possibile reindirizzare stdout su un file e quindi copiare stderr su stdout in modo da poterlo reindirizzare tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!

Ciò non consente di registrare i due flussi nello stesso file.
artistoex,

1
@artistoex: puoi semplicemente usare tee -alo stesso file usato 1>per sommare entrambi gli output nello stesso file. Qualcosa del tipo:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya

Non ho idea del perché, ma questo non funziona con wget -O - www.google.deme. (confronta con la soluzione seguente)
artistoex

4
L'uso tee -anon funzionerà in modo affidabile per avere lo stesso output che sarebbe sulla console se non fosse reindirizzato nulla. L'output che passa teepotrebbe essere leggermente ritardato rispetto all'output che proviene direttamente dal comando e quindi potrebbe apparire più avanti nel registro.
Gilles 'SO- smetti di essere malvagio' il

Questo non funziona per me, out + err.log è vuoto. E sì, voglio stderror e standard nello stesso file
Andreas

7

Sono stato in grado di ottenere ciò posizionando la seguente riga nella parte superiore di uno script bash:

exec 1>>log 2> >(tee -a log >&2)

Questo reindirizzerà stdout al file log( 1>>log), quindi collegherà stderr al file log( 2> >(tee -a log) e lo indirizzerà nuovamente a stderr ( >&2). In questo modo, ottengo un singolo file log, che mostra sia stdout che stderr in ordine, e anche stderr è visualizzato sullo schermo come al solito.

Il problema è che sembra funzionare solo quando aggiungo il file. Se non aggiungo, sembra che i due reindirizzamenti si intasino a vicenda, e ottengo solo l'ultimo output.


Qualche modo per ottenere questo per "modificare" le righe STDERR per includere una stringa personalizzata in modo da poter facilmente grep su di essa?
IMTheNachoMan,

1

Si desidera duplicare il flusso di errori in modo che appaia sia sulla console che nel file di registro. Lo strumento è tee, e tutto ciò che devi fare è applicarlo al flusso di errori. Sfortunatamente, non esiste un costrutto shell standard per convogliare il flusso di errori di un comando in un altro comando, quindi è necessario un piccolo riarrangiamento del descrittore di file.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log

Anche questo, per me non funziona del tutto, ottengo prima le linee stdout e poi tutte le linee stderr nel file 'log'. esempio: {{echo out; echo err 1> & 2; echo out2; echo err2 1> & 2; } 2> & 1> & 3 | tee / dev / tty; }> log 3> & 1
Andreas

1
@Andreas: Oh, duh, teeintroduce un ritardo anche qui. Non penso che ci sia una soluzione di shell pura. In effetti mi chiedo se esiste una soluzione senza modificare l'applicazione in qualche modo.
Gilles 'SO- smetti di essere malvagio' il

1

Per scrivere stderr sullo schermo e scrivere ENTRAMBE stderr e stdout in un file - E far sì che le linee per stderr e stdout escano nella stessa sequenza che farebbero se entrambi fossero scritti sullo schermo:

Risulta essere un problema difficile, in particolare la parte sull'avere la "stessa sequenza" che ti aspetteresti se li scrivessi semplicemente sullo schermo. In termini semplici: scrivi ciascuno nel suo file, esegui un po 'di magia in background per contrassegnare ogni riga (in ciascun file) con l'ora esatta in cui la riga è stata prodotta, quindi: "tail --follow" il file stderr sullo schermo , ma per vedere ENTRAMBE "stderr" e "stdout" insieme - in sequenza - ordina i due file (con marcature del tempo esatto su ciascuna riga) insieme.

Codice:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Sì, questo sembra elaborato e si traduce in 4 file di output (2 dei quali è possibile eliminare). Sembra che questo sia un problema difficile da risolvere, quindi ci sono voluti diversi meccanismi.

Alla fine, per vedere i risultati di ENTRAMBE stdout e stderr nella sequenza che ti aspetteresti, esegui questo:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

L'unico motivo per cui la sequenza è almeno molto vicina a come sarebbe stata se sia stdout che stderr fossero semplicemente andati sullo schermo è: ogni singola riga è contrassegnata da un timestamp fino al millisecondo.

Per vedere stderr sullo schermo mentre il processo procede, usa questo:

tail -f $pth/$ffn.out

Spero che aiuti qualcuno, che è arrivato qui molto tempo dopo che è stata posta la domanda originale.


+1 per il semplice sforzo, ho avuto un'idea simile (timestamp di entrambi i flussi via perl) ma stavo faticando a implementarlo. Investigherò se funziona per me (cioè se mantiene davvero l'ordine delle uscite, poiché altre soluzioni qui spostano un po 'le cose a causa dell'asincronicità tra etichettatura di stderr e stdout)
Olivier Dulac il

0

Lascia che fsia il comando che desideri venga eseguito, quindi questo

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

dovrebbe darti quello che desideri. Ad esempio wget -O - www.google.desarebbe simile a questo:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )

1
Questo funziona quasi per me, ma nel file di registro ottengo prima tutte le linee stdout e quindi tutte le linee stderror. Voglio che si alternino nell'ordine naturale mentre accadono.
Andreas,

0

Dato quello che ho letto qui, l'unica soluzione che posso vedere sarebbe quella di anteporre a ogni riga STOUT o STERR con un timeslice / timestamp. data +% s arriverebbe a secondi, ma questo rallenta per mantenere l'ordine. Inoltre, il registro dovrebbe essere in qualche modo ordinato. Più lavoro di quello che sono in grado di fare.


0

Ho lottato con questo preciso requisito e alla fine non sono riuscito a trovare una soluzione semplice. Quello che ho finito per fare invece è stato questo:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

Aggiunge stdout e stderr, nel giusto ordine interlacciato, al file di registro. E se si sono verificati errori (come determinato dallo stato di uscita del comando), invia l'intero output (stdout e stderr) a stdout, sempre nel giusto ordine interlacciato. Ho trovato questo un ragionevole compromesso per le mie esigenze. Se si desidera un file di registro a singola esecuzione anziché un file a più corse in crescita, è ancora più semplice:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log

0

stderr a comando sostituito tee -ae stdout aggiungendo allo stesso file:

./script.sh 2> >(tee -a outputfile) >>outputfile

e nota: anche per garantire l'ordine corretto (ma senza stderr-show) c'è il comando 'unbuffer' dagli strumenti 'prevedono', che simula tty e mantiene stdout / err in ordine come apparirebbe sul terminale:

unbuffer ./script.sh > outputfile
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.