Come posso pipe stderr e non stdout?


982

Ho un programma che scrive informazioni su stdoute stderr, e ho bisogno di greppassare attraverso ciò che sta arrivando a stderr , ignorando stdout .

Ovviamente posso farlo in 2 passaggi:

command > /dev/null 2> temp.file
grep 'something' temp.file

ma preferirei essere in grado di farlo senza i file temporanei. Ci sono trucchi intelligenti per le tubazioni?


Una domanda simile, ma mantenendo stdout: unix.stackexchange.com/questions/3514/…
joeytwiddle

Questa domanda era per Bash ma vale la pena menzionare questo articolo correlato per la shell Bourne / Almquist.
Stephen Niedzielski,

10
Mi aspettavo qualcosa di simile a questo: command 2| othercommand. Bash è così perfetto che lo sviluppo è terminato nel 1982, quindi non lo vedremo mai a Bash, temo.
Rolf,

Risposte:


1190

Per prima cosa reindirizza stderr a stdout: la pipe; quindi reindirizzare stdout a /dev/null(senza cambiare la direzione di stderr):

command 2>&1 >/dev/null | grep 'something'

Per i dettagli sul reindirizzamento I / O in tutta la sua varietà, consultare il capitolo Reindirizzamenti nel manuale di riferimento di Bash.

Si noti che la sequenza dei reindirizzamenti I / O viene interpretata da sinistra a destra, ma i tubi vengono impostati prima dell'interpretazione dei reindirizzamenti I / O. I descrittori di file come 1 e 2 sono riferimenti a descrizioni di file aperti. L'operazione 2>&1fa in modo che il descrittore di file 2 aka stderr si riferisca alla stessa descrizione di file aperta di cui al descrittore di file 1 aka stdout si riferisce attualmente (vedere dup2()e open()). L'operazione >/dev/nullquindi modifica il descrittore di file 1 in modo che faccia riferimento a una descrizione di file aperta /dev/null, ma ciò non cambia il fatto che il descrittore di file 2 si riferisce alla descrizione di file aperta a cui il descrittore di file 1 originariamente puntava, vale a dire la pipe.


44
mi sono imbattuto in / dev / stdout / dev / stderr / dev / stdin l'altro giorno, ed ero curioso di sapere se quelli sono buoni modi di fare la stessa cosa? Ho sempre pensato che 2> & 1 fossero un po 'offuscati. Quindi qualcosa del tipo: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons,

17
Puoi usare /dev/stdoutet al o usare /dev/fd/N. Saranno marginalmente meno efficienti a meno che la shell non li tratti come casi speciali; la pura notazione numerica non implica l'accesso ai file per nome, ma l'utilizzo dei dispositivi implica una ricerca per nome file. Se è possibile misurare ciò è discutibile. Mi piace la sintonia della notazione numerica, ma la sto usando da così tanto tempo (più di un quarto di secolo; ahi!) Che non sono qualificato per giudicare i suoi meriti nel mondo moderno.
Jonathan Leffler,

23
@Jonathan Leffler: Prendo un piccolo problema con la tua spiegazione in chiaro 'Reindirizza stderr su stdout e poi stdout su / dev / null' - Dato che uno deve leggere le catene di reindirizzamento da destra a sinistra (non da sinistra a destra), noi dovremmo anche adattare la nostra spiegazione in chiaro a questo: "Reindirizzare stdout su / dev / null, e quindi stderr su dove si trovava stdout" .
Kurt Pfeifle,

116
@KurtPfeifle: au contraire! Bisogna leggere le catene di reindirizzamento da sinistra a destra poiché è così che la shell le elabora. La prima operazione è la 2>&1, che significa "collega stderr al descrittore di file che stdout sta attualmente eseguendo". La seconda operazione è "cambia stdout in modo che vada /dev/null", lasciando stderr andando allo stdout originale, il pipe. La shell divide prima le cose sul simbolo della pipe, quindi il reindirizzamento della pipe si verifica prima dei reindirizzamenti 2>&1o >/dev/null, ma questo è tutto; le altre operazioni sono da sinistra a destra. (Da destra a sinistra non funzionerebbe.)
Jonathan Leffler

14
La cosa che mi sorprende davvero di questo è che funziona anche su Windows (dopo aver rinominato /dev/nulll'equivalente di Windows nul).
Michael Burr,

364

In alternativa, per scambiare l'output da errore standard e output standard, utilizzare:

command 3>&1 1>&2 2>&3

Questo crea un nuovo descrittore di file (3) e lo assegna nella stessa posizione di 1 (output standard), quindi assegna fd 1 (output standard) nella stessa posizione di fd 2 (errore standard) e infine assegna fd 2 (errore standard ) nello stesso posto di fd 3 (output standard).

L'errore standard è ora disponibile come output standard e il vecchio output standard viene conservato nell'errore standard. Questo può essere eccessivo, ma si spera che fornisca maggiori dettagli sui descrittori di file Bash (ce ne sono nove disponibili per ogni processo).


100
Un'ultima modifica sarebbe quella 3>&-di chiudere il descrittore di riserva che hai creato da stdout
Jonathan Leffler,

1
Possiamo creare un descrittore di file che ha stderre un altro che ha la combinazione di stderre stdout? In altre parole, puoi stderrandare a due file diversi contemporaneamente?
Stuart

Quanto segue stampa ancora errori su stdout. Cosa mi sto perdendo? ls -l not_a_file 3> & 1 1> & 2 2> & 3> errors.txt
user48956

1
@ JonasDahlbæk: il tweak è principalmente una questione di ordine. In situazioni veramente arcane, potrebbe fare la differenza tra un processo che rileva e non rileva EOF, ma ciò richiede circostanze molto peculiari.
Jonathan Leffler

1
Attenzione : questo presuppone che FD 3 non sia già in uso, non lo chiuda e non annulli lo scambio dei descrittori di file 1 e 2, quindi non è possibile continuare a eseguire il pipe su un altro comando. Vedere questa risposta per ulteriori dettagli e soluzioni. Per una sintassi molto più pulita per {ba, z} sh, vedi questa risposta .
Tom Hale,

218

In Bash, puoi anche reindirizzare a una subshell usando la sostituzione del processo :

command > >(stdlog pipe)  2> >(stderr pipe)

Per il caso in questione:

command 2> >(grep 'something') >/dev/null

1
Funziona molto bene per l'output sullo schermo. Hai idea del perché il contenuto non preparato venga visualizzato di nuovo se reindirizzo l'output grep in un file? Dopo command 2> >(grep 'something' > grep.log)grep.log contiene lo stesso output di ungrepped.log dicommand 2> ungrepped.log
Tim

9
Usa 2> >(stderr pipe >&2). Altrimenti l'uscita del "tubo stderr" passerà attraverso il "tubo stdlog".
ceving

sì ! 2> >(...), funziona, ci ho provato, 2>&1 > >(...)ma non è così
datdinhquoc

Ecco un piccolo esempio che mi può aiutare la prossima volta che cerco come farlo. Considera quanto segue ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) In questo caso, volevo vedere anche quelli che stavano venendo fuori come errori sulla mia console. Ma STDOUT stava andando al file di output. Quindi all'interno della sotto-shell, è necessario reindirizzare lo STDOUT su STDERR tra parentesi. Mentre funziona, l'output STDOUT dal teecomando termina alla fine del out-content.txtfile. Mi sembra incoerente.
sarà il

@datdinhquoc L'ho fatto in qualche modo2>&1 1> >(dest pipe)
Alireza Mohamadi il

195

Combinando la migliore di queste risposte, se lo fai:

command 2> >(grep -v something 1>&2)

... quindi tutto lo stdout viene conservato come stdout e tutto lo stderr viene conservato come stderr, ma non vedrai alcuna riga in stderr contenente la stringa "qualcosa".

Questo ha il vantaggio unico di non invertire o scartare stdout e stderr, né di metterli insieme o di usare file temporanei.


Non è command 2> >(grep -v something)(senza 1>&2) la stessa cosa?
Francesc Rosas,

11
No, senza quello, lo stderr filtrato finisce per essere indirizzato a stdout.
Pinko,

1
Questo è ciò di cui avevo bisogno - il file tar output "è cambiato mentre lo leggiamo" per una directory sempre, quindi voglio solo filtrare quella riga ma vedere se si verificano altri errori. Quindi tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)dovrebbe funzionare.
razziato il

è sbagliato dire "a cominciare dalla stringa". Non c'è nulla nella sintassi presentata di grep che possa escludere solo le righe che iniziano con la stringa data. Le righe verranno escluse se contengono la stringa specificata in un punto qualsiasi di esse.
Mike Nakis,

@MikeNakis grazie - risolto! (Era rimasto della mia bozza di risposta originale, in cui aveva senso ...)
Pinko

102

È molto più facile visualizzare le cose se pensi a cosa sta realmente succedendo con "reindirizzamenti" e "pipe". I reindirizzamenti e le pipe in bash fanno una cosa: modifica dove i descrittori del file di processo 0, 1 e 2 puntano a (vedi / proc / [pid] / fd / *).

Quando una pipe o "|" l'operatore è presente sulla riga di comando, la prima cosa che succede è che bash crea un FIFO e punta il FD 1 del comando sul lato sinistro verso questo FIFO, e punta FD 0 del comando sul lato destro sullo stesso FIFO.

Successivamente, gli operatori di reindirizzamento per ciascun lato vengono valutati da sinistra a destra e vengono utilizzate le impostazioni correnti ogni volta che si verifica la duplicazione del descrittore. Questo è importante perché dalla prima installazione del tubo, l'FD1 (lato sinistro) e l'FD0 (lato destro) sono già cambiati da quello che avrebbero potuto essere normalmente, e qualsiasi duplicazione di questi rifletterà quel fatto.

Pertanto, quando si digita qualcosa di simile al seguente:

command 2>&1 >/dev/null | grep 'something'

Ecco cosa succede, in ordine:

  1. viene creato un pipe (fifo). "comando FD1" è puntato su questa pipe. Anche "grep FD0" è puntato su questa pipe
  2. "comando FD2" è indicato dove "comando FD1" indica attualmente (la pipe)
  3. "comando FD1" è puntato su / dev / null

Quindi, tutto l'output che "command" scrive su FD 2 (stderr) si dirige verso la pipe e viene letto da "grep" dall'altra parte. Tutto l'output che "comando" scrive nel suo FD 1 (stdout) si dirige verso / dev / null.

Se invece, esegui quanto segue:

command >/dev/null 2>&1 | grep 'something'

Ecco cosa succede:

  1. viene creato un pipe e "command FD 1" e "grep FD 0" vengono puntati su di esso
  2. "comando FD 1" è puntato su / dev / null
  3. "comando FD 2" è indicato dove attualmente punta FD 1 (/ dev / null)

Quindi, tutto stdout e stderr dal "comando" vanno a / dev / null. Nulla va alla pipa, e quindi "grep" si chiuderà senza visualizzare nulla sullo schermo.

Si noti inoltre che i reindirizzamenti (descrittori di file) possono essere di sola lettura (<), sola scrittura (>) o lettura-scrittura (<>).

Un'ultima nota. Se un programma scrive qualcosa su FD1 o FD2, dipende interamente dal programmatore. La buona pratica di programmazione impone che i messaggi di errore debbano andare a FD 2 e l'output normale a FD 1, ma spesso troverete una programmazione sciatta che mescola i due o ignora in altro modo la convenzione.


6
Davvero una bella risposta. Il mio unico suggerimento sarebbe quello di sostituire il tuo primo utilizzo di "fifo" con "fifo (una pipa denominata)". Sto usando Linux da un po ', ma in qualche modo non sono mai riuscito a capire che è un altro termine chiamato pipe. Questo mi avrebbe salvato dal cercare, ma poi non avrei imparato le altre cose che ho visto quando l'ho scoperto!
Mark Edington,

3
@MarkEdington Si noti che FIFO è solo un altro termine per pipe denominata nel contesto di pipe e IPC . In un contesto più generale, FIFO significa First in, first out, che descrive l'inserimento e la rimozione da una struttura di dati della coda.
Loomchild,

5
@Loomchild Certo. Il punto del mio commento è stato che, anche come sviluppatore esperto, non avevo mai visto FIFO usato come sinonimo di named pipe. In altre parole, non lo sapevo: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Chiarire che nella risposta mi avrebbe risparmiato tempo.
Mark Edington,

39

Se stai usando Bash, usa:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines


4
No, |&è uguale al 2>&1quale combina stdout e stderr. La domanda ha chiesto esplicitamente l'output senza stdout.
Profpatsch,

3
„Se si utilizza '| &', l'errore standard di command1 è collegato all'ingresso standard di command2 attraverso la pipe; è una scorciatoia per 2> & 1 | ” Tratto letteralmente dal quarto paragrafo al tuo link.
Profpatsch,

9
@Profpatsch: la risposta di Ken è corretta, guarda che reindirizza stdout su null prima di combinare stdout e stderr, quindi otterrai in pipe solo lo stderr, perché lo stdout è stato precedentemente rilasciato su / dev / null.
Luciano,

3
Ma ho ancora trovato la risposta è sbagliata, >/dev/null |&si espandono per >/dev/null 2>&1 | e mezzi stdout inode è vuoto al tubo perché nessuno (# 1 # 2 sia legato a / dev / null inode) è legata a stdout inode (ad esempio, ls -R /tmp/* >/dev/null 2>&1 | grep idarà vuoto, ma ls -R /tmp/* 2>&1 >/dev/null | grep isarà lascia # 2 che si lega all'inode stdout verrà convogliato).
Frutta

3
Ken Sharp, ho testato e ( echo out; echo err >&2 ) >/dev/null |& grep "."non fornisce output (dove vogliamo "err"). man bashdice Se | & viene utilizzato ... è una scorciatoia per 2> & 1 |. Questo reindirizzamento implicito dell'errore standard sull'output standard viene eseguito dopo qualsiasi reindirizzamento specificato dal comando. Quindi prima reindirizziamo FD1 del comando su null, quindi reindirizziamo FD2 del comando su dove FD1 puntava, cioè. null, quindi FD0 di grep non riceve input. Consulta stackoverflow.com/a/18342079/69663 per una spiegazione più approfondita.
unhammer,

11

Per coloro che vogliono reindirizzare permanentemente stdout e stderr su file, grep su stderr, ma mantenere lo stdout per scrivere messaggi su un tty:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3

6

Questo reindirizzerà command1 stderr a command2 stdin, lasciando command1 stdout così com'è.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Tratto da LDP


2

Ho appena trovato una soluzione per l'invio stdouta un comando e stderrad un altro, usando named pipe.

Ecco qui.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

È probabilmente una buona idea rimuovere le pipe nominate in seguito.


0

Puoi usare la shell rc .

Innanzitutto installa il pacchetto (è inferiore a 1 MB).

Questo è un esempio di come scartare l'output standard e l'errore pipe standard per grep in rc:

find /proc/ >[1] /dev/null |[2] grep task

Puoi farlo senza lasciare Bash:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

Come avrai notato, puoi specificare il descrittore di file che vuoi reindirizzare usando le parentesi dopo la pipe.

I descrittori di file standard sono numerati come tali:

  • 0: input standard
  • 1: uscita standard
  • 2: errore standard

-3

Provo a seguire, trovo che funzioni anche,

command > /dev/null 2>&1 | grep 'something'

Non funziona Invia semplicemente stderr al terminale. Ignora il tubo.
Tripp Kinetics,
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.