Come faccio a scrivere stderr in un file mentre utilizzo "tee" con una pipe?


544

So usare teeper scrivere l'output ( STDOUT) di aaa.sha bbb.out, mentre lo visualizzo ancora nel terminale:

./aaa.sh | tee bbb.out

Come potrei ora scrivere anche STDERRsu un file chiamato ccc.out, pur avendo ancora visualizzato?


2
Per chiarire: vuoi che stderr vada sullo schermo e sul file?
Charles Duffy,

L'ho fatto, modificherò il mio post per chiarirlo. Credo che la soluzione di lhunath sarà sufficiente. Grazie per l'aiuto a tutti!
jparanich,

Risposte:


786

Presumo che tu voglia vedere ancora STDERR e STDOUT sul terminale. Potresti cercare la risposta di Josh Kelley, ma trovo che mantenere uno tailsfondo in giro che produca il tuo file di registro sia molto complicato e ingombrante. Si noti come è necessario mantenere un FD exra e procedere successivamente alla pulizia uccidendolo e tecnicamente dovrebbe farlo in untrap '...' EXIT .

C'è un modo migliore per farlo e l'hai già scoperto: tee .

Solo, invece di usarlo solo per il tuo stdout, hai un tee per stdout e uno per stderr. Come lo farai? Sostituzione del processo e reindirizzamento dei file:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Dividiamolo e spieghiamo:

> >(..)

>(...)(sostituzione di processo) crea un FIFO e consente di teeascoltarlo. Quindi, utilizza >(reindirizzamento dei file) per reindirizzare lo STDOUT commandal FIFO su cui il primo teesta ascoltando.

Stessa cosa per il secondo:

2> >(tee -a stderr.log >&2)

Usiamo nuovamente la sostituzione di processo per creare un teeprocesso che legge da STDIN e lo scarica stderr.log. teerestituisce il suo input su STDOUT, ma poiché il suo input è il nostro STDERR, vogliamo reindirizzare nuovamente lo teeSTDOUT al nostro STDERR. Quindi usiamo il reindirizzamento dei file per reindirizzare lo commandSTDERR sull'input della FIFO ( teeSTDIN).

Vedi http://mywiki.wooledge.org/BashGuide/InputAndOutput

La sostituzione del processo è una di quelle cose davvero adorabili che si ottiene come bonus di scegliere bashcome shell invece di sh(POSIX o Bourne).


In sh, dovresti fare le cose manualmente:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"

5
Ho provato questo: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)che funziona, ma attende input. C'è un semplice motivo per cui questo accade?
Justin,

1
@SillyFreak Non capisco cosa vuoi fare o quale sia il problema che stai riscontrando. test di eco; exit non produce alcun output su stdout, quindi err rimarrà vuoto.
lhunath,

1
grazie per quel commento; Ho capito quale fosse il mio errore logico in seguito: quando invocato come una shell interattiva, bash stampa un prompt dei comandi e fa eco all'uscita su stderr. Tuttavia, se reindirizza stderr, bash si avvia come non interattivo per impostazione predefinita; confrontare /bin/bash 2> erre/bin/bash -i 2> err
Silly Freak

15
E per coloro che "vedere è credere", un rapido test:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Matthew Wilcoxson,

2
Wow . Buona risposta: "Dividiamolo e spieghiamo" +1
jalanb,

674

perché non semplicemente:

./aaa.sh 2>&1 | tee -a log

Questo reindirizza semplicemente stderra stdout, quindi tee fa eco sia al registro che allo schermo. Forse mi manca qualcosa, perché alcune delle altre soluzioni sembrano davvero complicate.

Nota: dalla versione 4 di bash è possibile utilizzare |&come abbreviazione di 2>&1 |:

./aaa.sh |& tee -a log

85
Funziona bene se si desidera che sia stdout (canale 1) che stderr (canale 2) siano registrati nello stesso file (un singolo file contenente la combinazione di stdout e sterr). L'altra soluzione più complicata consente di separare stdout e stderr in 2 file diversi (stdout.log e stderr.log, rispettivamente). A volte è importante, a volte no.
Tyler Rick,

19
Le altre soluzioni sono molto più complicate del necessario in molti casi. Questo funziona perfettamente per me.
dkamins,

13
Il problema con questo metodo è che si perde il codice di uscita / stato dal processo aaa.sh, che può essere importante (ad es. Quando si usa in un makefile). Non hai questo problema con la risposta accettata.
Stefaan,

9
se non ti dispiace unire stdout / stderr allora ./aaa.sh |& tee aaa.logfunziona (in bash).
jfs,

5
@Stefaan Credo che puoi mantenere lo stato di uscita se anteponi la catena di comandi set -o pipefailseguita da ;o &&se non sbaglio.
David,

59

Questo può essere utile per chi lo trova tramite google. Basta decommentare l'esempio che si desidera provare. Naturalmente, sentiti libero di rinominare i file di output.

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}

6
No, e immagino che exec possa usare qualche spiegazione. exec >significa, spostare la destinazione di un descrittore di file su una determinata destinazione. Il valore predefinito è 1, quindi exec > /dev/nullsposta l'output di stdout su / dev / null d'ora in poi in questa sessione. I descrittori di file correnti per questa sessione possono essere visualizzati facendo ls -l /dev/fd/. Provalo! Quindi vedere cosa succede quando si emette exec 2>/tmp/stderr.log.Inoltre, exec 3>&1significa creare un nuovo descrittore di file con il numero 3 e reindirizzarlo alla destinazione del descrittore di file 1. Nell'esempio, la destinazione era la schermata quando è stato emesso il comando.
Drumfire

L'esempio per mostrare stdout e stderr sia sullo schermo che su file separati è fantastico !!! Molte grazie!
Marcello de Sales,

21

Per reindirizzare stderr su un file, visualizzare stdout sullo schermo e anche salvare stdout su un file:

./aaa.sh 2> ccc.out | tee ./bbb.out

EDIT : per visualizzare sia stderr che stdout sullo schermo e anche salvarli entrambi in un file, puoi usare il reindirizzamento I / O di bash :

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1

1
Mi aspetto che l'utente desideri che stderr vada alla propria console oltre al file, sebbene tale non sia stato esplicitamente specificato.
Charles Duffy,

2
Avrei dovuto essere più chiaro, volevo anche stderr sullo schermo. Mi piaceva ancora la soluzione di Josh Kelley, ma ho trovato lhunath per soddisfare di più le mie esigenze. Grazie ragazzi!
jparanich,

18

In altre parole, vuoi reindirizzare stdout in un filtro ( tee bbb.out) e stderr in un altro filtro ( tee ccc.out). Non esiste un modo standard per reindirizzare qualsiasi cosa diversa da stdout in un altro comando, ma puoi aggirare questo manipolando i descrittori di file.

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

Vedi anche Come grep flusso di errori standard (stderr)? e quando useresti un descrittore di file aggiuntivo?

In bash (e ksh e zsh), ma non in altre shell POSIX come dash, è possibile utilizzare la sostituzione del processo :

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

Attenzione che in bash, questo comando ritorna non appena ./aaa.shtermina, anche se i teecomandi sono ancora eseguiti (ksh e zsh attendono i sottoprocessi). Questo potrebbe essere un problema se fai qualcosa del genere ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. In tal caso, utilizzare invece il descrittore di file giocoleria o ksh / zsh.


4
Questa sembra l'unica risposta che consente di mantenere i flussi stdout / stderr così come sono (ad es. Non unirli). Dolce!
user1338062,

L'approccio più semplice per sh, utile per i lavori cron, in cui la sostituzione del processo non è disponibile.
Roger Dueck,

13

Se si utilizza bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

Il credito (non rispondendo dalla cima della mia testa) va qui: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html


5

Nel mio caso, uno script eseguiva il comando mentre reindirizzava sia stdout che stderr su un file, qualcosa del tipo:

cmd > log 2>&1

Avevo bisogno di aggiornarlo in modo tale che in caso di errore, eseguire alcune azioni in base ai messaggi di errore. Ovviamente potrei rimuovere il dup 2>&1e catturare lo stderr dallo script, ma poi i messaggi di errore non andranno nel file di registro come riferimento. Mentre la risposta accettata da @lhunath dovrebbe fare lo stesso, reindirizza stdoute stderrsu file diversi, che non è quello che voglio, ma mi ha aiutato a trovare la soluzione esatta di cui ho bisogno:

(cmd 2> >(tee /dev/stderr)) > log

Con quanto sopra, il registro avrà una copia di entrambi stdoute stderrposso catturare stderrdal mio script senza doversi preoccupare stdout.


4

Di seguito funzionerà per KornShell (ksh) in cui la sostituzione del processo non è disponibile,

# create a combined(stdin and stdout) collector
exec 3 <> combined.log

# stream stderr instead of stdout to tee, while draining all stdout to the collector
./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3

# cleanup collector
exec 3>&-

Il vero trucco qui, è la sequenza della 2>&1 1>&3quale nel nostro caso reindirizza il stderrto stdoute reindirizza il stdoutto al descrittore 3. A questo punto i stderre stdoutnon sono ancora combinati.

In effetti, il simbolo stderr(as stdin) viene passato a teedove accede stderr.loge reindirizza anche al descrittore 3.

E il descrittore 3lo registra combined.logcontinuamente. Quindi combined.logcontiene entrambi stdoute stderr.


Nifty! Peccato che è sottovalutato perché è una buona opzione. Ho KSH, a proposito :)
runlevel0

2

Se stai usando zsh , puoi usare più reindirizzamenti, quindi non hai nemmeno bisogno di tee:

./cmd 1>&1 2>&2 1>out_file 2>err_file

Qui stai semplicemente reindirizzando ogni flusso a se stesso e al file di destinazione.


Esempio completo

% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err

Si noti che questo richiede l' MULTIOSopzione da impostare (che è l'impostazione predefinita).

MULTIOS

Eseguire tees o cats impliciti quando si tentano reindirizzamenti multipli (consultare Reindirizzamento ).

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.