Come reindirizzare stdout su un file e stdout + stderr su un altro?


32

Come posso raggiungere

cmd >> file1 2>&1 1>>file2

Cioè, lo stdout e lo stderr dovrebbero reindirizzare a un file (file1) e solo lo stdout (file2) dovrebbe reindirizzare a un altro (entrambi in modalità append)?

Risposte:


41

Il problema è che quando si reindirizza l'output, non è più disponibile per il reindirizzamento successivo. È possibile reindirizzare teein una subshell per mantenere l'output per il secondo reindirizzamento:

( cmd | tee -a file2 ) >> file1 2>&1

o se ti piace vedere l'output nel terminale:

( cmd | tee -a file2 ) 2>&1 | tee -a file1

Per evitare di aggiungere lo stderr del primo teea file1, dovresti reindirizzare lo stderr del tuo comando su un descrittore di file (ad es. 3), e successivamente aggiungerlo di nuovo a stdout:

( 2>&3 cmd | tee -a file2 ) >> file1 3>&1
# or
( 2>&3 cmd | tee -a file2 ) 3>&1 | tee -a file1

(grazie @ fra-san)


16

Con zsh:

cmd >& out+err.log > out.log

In modalità append:

cmd >>& out+err.log >> out.log

In zsh, e purché l' mult_iosopzione non sia stata disabilitata, quando un descrittore di file (qui 1) viene reindirizzato più volte per la scrittura, la shell implementa un built-in teeper duplicare l'output su tutte le destinazioni.


Non riesco a capire cosa out+erre cosa outsignifichi qui. Nomi dei file? Stream da reindirizzare?
gronostaj,

@gronostaj Pensa che il comando reciticmd >& file1 > file2
Isaac

Questa soluzione manterrà l'ordine in cui è stato generato l'output. Per archiviare effettivamente lo stdout e lo stderr (in questo ordine) è necessario un approccio diverso.
Isaac,

1
@Isaac, l'ordine non verrà necessariamente conservato poiché l'output stdout passerà attraverso una pipe (a un processo che lo inoltra a ciascun file) mentre l'output stderr andrà direttamente al file. In ogni caso, non sembra che l'OP abbia chiesto che l'uscita stderr venga dopo quella stdout.
Stéphane Chazelas,

3

Puoi: taggare stdout (usando un sed UNBUFFERED, cioè:), sed -u ...anche stderr passa a stdout (senza tag, dato che non ha passato quel tag tagging), e quindi essere in grado di differenziare i 2 nel file di registro risultante.

Quanto segue: è lento (può essere seriamente ottimizzato, usando ad esempio uno script perl anziché il mentre ...; do ...; fatto, ad esempio, che genererà sottotitoli e comandi in ogni riga!), Strano (sembra che io abbia bisogno dei 2 {} stadi per rinominare uno stdout e poi nell'altro aggiungere lo "falled" allo stderr), ecc. Ma è: una " prova del concetto ", che cercherà di mantenere ordina l'output il più possibile di stdout e stderr:

#basic principle (some un-necessary "{}" to visually help see the layers):
# { { complex command ;} | sed -e "s/^/TAGstdout/" ;} 2>&1 | read_stdin_and_redispatch

#exemple:
# complex command = a (slowed) ls of several things (some existing, others not)
#  to see if the order of stdout&stderr is kept

#preparation, not needed for the "proof of concept", but needed for our specific exemple setup:
\rm out.file out_AND_err.file unknown unknown2 
touch existing existing2 existing3

#and the (slow, too many execs, etc) "proof of concept":
uniquetag="_stdout_" # change this to something unique, that will NOT appear in all the commands outputs... 
                     # avoid regexp characters ("+" "?" "*" etc) to make it easy to remove with another sed later on.

{
   { for f in existing unknown existing2 unknown2 existing3 ; do ls -l "$f" ; sleep 1; done ;
   } | sed -u -e "s/^/${uniquetag}/" ;
} 2>&1 | while IFS="" read -r line ; do
    case "$line" in
       ${uniquetag}*) printf "%s\n" "$line" | tee -a out_AND_err.file | sed -e "s/^${uniquetag}//" >> out.file ;; 
        *)            printf "%s\n" "$line"       >> out_AND_err.file ;;   
    esac; 
done;

# see the results:
grep "^" out.file out_AND_err.file

Questo è davvero difficile da capire. (1) Perché usi un caso d'uso così complicato ( ls unknown) per stampare qualcosa su stderr? >&2 echo "error"andrebbe bene. (2) teepuò aggiungere a più file contemporaneamente. (3) Perché non solo catinvece di grep "^"? (4) il tuo script fallirà quando inizia l'output stderr _stdout_. (5) Perché?
pLumo,

@RoVo: le prime 2 righe commentate mostrano l'algoritmo, più semplice dell'esempio di prova del concetto. 1): questo ls loopemetterà sia su stdout che su stderr, mixati (in alternativa), in un ordine controllato; in modo che possiamo verificare che abbiamo mantenuto quell'ordinamento stderr / stdout nonostante il tagging di stdout 2): gnu tail, forse, ma no coda regolare (ex, su aix.). 3): grep "^" mostra anche entrambi i nomi dei file. 4): può essere modificato dalla variabile. 5): l'esempio contorto funziona su vecchi osi (ex, vecchio aix) dove l'ho provato (nessun perl disponibile).
Olivier Dulac

(1) l'eco multiplo su stderr e stout andrebbe bene, ma va bene, non importante, sarebbe solo più facile da leggere. (3) d'accordo, (4) certo, ma fallirà se inizia con qualunque cosa la variabile contenga. (5) vedo.
pLumo,

@RoVo sono d'accordo con il tuo 1). per 4), la variabile potrebbe essere complessa quanto basta per far scomparire il pb (es: uniquetag="banaNa11F453355B28E1158D4E516A2D3EDF96B3450406...)
Olivier Dulac

1
Certo, non è molto probabile, ma comunque potrebbe introdurre un problema di sicurezza in un secondo momento. E quindi potresti voler rimuovere quella stringa prima di stampare su file ;-)
pLumo

2

Se l'ordine di output deve essere: stdout, allora stderr ; non esiste una soluzione solo con il reindirizzamento.
Lo stderr deve essere archiviato in un file temporale

cmd 2>>file-err | tee -a file1 >>file2
cat file-err >> file1
rm file-err

Descrizione:

L'unico modo per reindirizzare un output (un fd come stdout o stderr) su due file è riprodurlo. Il comando teeè lo strumento corretto per riprodurre il contenuto di un descrittore di file. Quindi, un'idea iniziale di avere un output su due file sarebbe quella di usare:

... |  tee file1 file2

Ciò riproduce lo stdin di tee su entrambi i file (1 e 2) lasciando l'output di tee ancora inutilizzato. Ma dobbiamo aggiungere (usare -a) e solo una copia. Questo risolve entrambi i problemi:

... | tee -a file1 >>file2

Per fornire teestdout (quello da ripetere) dobbiamo consumare stderr direttamente dal comando. In un modo, se l'ordine non è importante (l'ordine di output (molto probabilmente) sarà preservato come generato, qualunque sia l'output prima verrà memorizzato per primo). O:

  1. cmd 2>>file1 | tee -a file2 >>file1
  2. cmd 2>>file1 > >( tee -a file2 >>file1 )
  3. ( cmd | tee -a file2 ) >> file1 2>&1

L'opzione 2 funziona solo in alcune shell. L'opzione 3 usa una sottostruttura aggiuntiva (più lenta) ma usa i nomi dei file solo una volta.

Ma se stdout deve essere il primo (qualunque sia l'output dell'ordine generato) dobbiamo archiviare stderr per aggiungerlo al file alla fine (prima soluzione pubblicata).


1
O conservare in memoria come spongefa:(cmd | tee -a out >> out+err) 2>&1 | sponge >> out+err
Stéphane Chazelas

1

Nell'interesse della diversità:

Se il tuo sistema supporta /dev/stderr, quindi

(cmd | tee -a /dev/stderr) 2>> file1 >> file2

funzionerà. L'output standard di cmd viene inviato sia allo stdout che allo stderr della pipeline. L'errore standard del cmdbypassa il tee ed esce lo stderr della pipeline.

Così

  • lo stdout della pipeline è solo lo stdout di cmd, e
  • lo stderr della pipeline è lo stdout e lo stderr del cmd, mescolati.

È quindi una semplice questione di inviare quei flussi ai file corretti.

Come in quasi tutti gli approcci come questo (inclusa la risposta di Stéphane ), le file1linee potrebbero non funzionare.

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.