Come grep flusso di errori standard (stderr)?


76

Sto usando ffmpeg per ottenere le meta informazioni di una clip audio. Ma non sono in grado di farlo.

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --prefix=/usr --bindir=/usr/bin 
      --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib
      --mandir=/usr/share/man --arch=i386 --extra-cflags=-O2 
      ...

Ho controllato, questo output di ffmpeg è diretto a stderr.

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

Quindi penso che grep non sia in grado di leggere il flusso di errori per rilevare le linee corrispondenti. Come possiamo consentire a grep di leggere il flusso di errori?

Usando il collegamento nixCraft , ho reindirizzato il flusso di errori standard al flusso di output standard, quindi grep ha funzionato.

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

E se non volessimo reindirizzare stderr a stdout?


1
Credo che greppossa funzionare solo su stdout (anche se non riesco a trovare la fonte canonica per eseguirne il backup), il che significa che qualsiasi stream deve prima essere convertito in stdout.
Stefan Lasiewski,

9
@Stefan: greppuò funzionare solo su stdin. È la pipe creata dalla shell che collega lo stdin di grep allo stdout dell'altro comando. E la shell può collegare solo uno stdout a uno stdin.
Gilles 'SO- smetti di essere malvagio' il

Spiacenti, hai ragione. Penso che sia quello che intendevo dire, non ci ho pensato. Grazie @Giles.
Stefan Lasiewski,

Vuoi che stampi ancora stdout?
Mikel,

Risposte:


52

Se stai usando bashperché non utilizzare pipe anonime, in sostanza abbreviazione per ciò che phunehehe ha detto:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)


3
+1 bello! Solo bash, ma molto più pulito delle alternative.
Mikel

1
+1 L'ho usato per controllare l' cpoutputcp -r dir* to 2> >(grep -v "svn")
Betlista,

5
Se si desidera nuovamente l'output reindirizzato filtrato su stderr, aggiungere a >&2, ad es.command 2> >(grep something >&2)
1616

2
Per favore, spiega come funziona. 2>reindirizza stderr su file, ho capito. Questo legge dal file >(grep -i Duration). Ma il file non viene mai archiviato? Come si chiama questa tecnica in modo che io possa leggere di più al riguardo?
Marko Avlijaš,

1
È "zucchero sintattico" per creare e una pipe (non file) e una volta completata rimuovere quella pipe. Sono effettivamente anonimi perché non hanno un nome nel filesystem. Bash chiama questo processo di sostituzione.
Jé Queue,

48

Nessuno dei soliti gusci (anche zsh) consente tubi diversi dallo stdout allo stdin. Ma tutte le shell in stile Bourne supportano la riassegnazione dei descrittori di file (come in 1>&2). Quindi puoi deviare temporaneamente stdout su fd 3 e stderr su stdout, e successivamente rimettere fd 3 su stdout. Se stuffproduce dell'output su stdout e dell'output su stderr e si desidera applicare filterl'output dell'errore lasciando intatto l'output standard, è possibile utilizzare { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1.

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error

Questo approccio funziona. Sfortunatamente, nel mio caso, se viene restituito un valore di ritorno diverso da zero, viene perso - il valore restituito è 0 per me. Questo potrebbe non succedere sempre, ma succede nel caso in cui sto guardando. C'è un modo per salvarlo?
Faheem Mitha,

2
@FaheemMitha Non sono sicuro di quello che stai facendo, ma forse pipestatussarebbe d'aiuto
Gilles 'SO- smetti di essere malvagio'

1
@FaheemMitha, set -o pipefailpuò anche essere utile qui, a seconda di cosa vuoi fare con lo stato di errore. (Ad esempio se hai set -eattivato l'errore per errori, probabilmente vorrai set -o pipefailanche tu .)
Wildcard

@Wildcard Sì, ho set -eattivato per non riuscire su eventuali errori.
Faheem Mitha,

La rcshell consente il piping stderr. Vedi la mia risposta qui sotto.
Rolf

20

Questo è simile al "trucco dei file temporanei" di phunehehe, ma utilizza invece una pipe denominata, che consente di ottenere risultati leggermente più vicini a quando vengono emessi, il che può essere utile per i comandi di lunga durata:

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

In questa costruzione, stderr verrà indirizzato alla pipe denominata "mypipe". Poiché grepè stato chiamato con un argomento file, non cercherà STDIN per il suo input. Sfortunatamente, dovrai ancora ripulire quella pipa denominata una volta terminato.

Se stai usando Bash 4, c'è una sintassi del collegamento per command1 2>&1 | command2, che è command1 |& command2. Tuttavia, credo che questa sia puramente una scorciatoia di sintassi, stai ancora reindirizzando STDERR a STDOUT.



1
Questa |&sintassi è perfetta per ripulire le tracce di stack di Ruby stderr gonfiate. Finalmente riesco ad accettare quelli senza troppa seccatura.
pgr,

8

Le risposte di Gilles e Stefan Lasiewski sono entrambe buone, ma in questo modo è più semplice:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

Presumo che tu non voglia ffmpeg'sstampare stdout.

Come funziona:

  • prima i tubi
    • ffmpeg e grep vengono avviati, con lo stdout di ffmpeg che va allo stdin di grep
  • reindirizzamenti successivi, da sinistra a destra
    • stderr di ffmpeg è impostato su qualunque sia il suo stdout (attualmente la pipe)
    • Lo stdout di ffmpeg è impostato su / dev / null

Questa descrizione mi confonde. Gli ultimi due proiettili mi fanno pensare "reindirizzare stderr su stdout", quindi "reindirizzare stdout (con stderr, ora) su / dev / null". Tuttavia, questo non è ciò che sta realmente facendo. Queste affermazioni sembrano essere invertite.
Steve,

1
@Steve Non c'è "con stderr" nel secondo punto. Hai visto unix.stackexchange.com/questions/37660/order-of-redirections ?
Mikel,

No, voglio dire, questa è la mia interpretazione di come l'hai descritta in inglese. Il link che hai fornito è molto utile, comunque.
Steve,

Perché è stato necessario essere reindirizzato a / dev / null?
Kanwaljeet Singh,

7

Vedi sotto per lo script usato in questi test.

Grep può funzionare solo su stdin, quindi è necessario convertire il flusso stderr in una forma che Grep può analizzare.

Normalmente, stdout e stderr sono entrambi stampati sullo schermo:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

Per nascondere stdout, ma stampare comunque stderr, procedere come segue:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

Ma grep non funzionerà su stderr! Ci si aspetterebbe che il comando seguente elimini le righe che contengono "err", ma non lo è.

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

Ecco la soluzione

La seguente sintassi di Bash nasconderà l'output su stdout, ma mostrerà comunque stderr. Per prima cosa eseguiamo il pipe di stdout in / dev / null, quindi convertiamo stderr in stdout, perché le pipe Unix funzioneranno solo su stdout. Puoi ancora grep il testo.

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(Si noti che il comando sopra è diverso quindi ./command >/dev/null 2>&1, che è un comando molto comune).

Ecco lo script utilizzato per i test. Questo stampa una riga su stdout e una riga su stderr:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0

Se cambi i reindirizzamenti, non hai bisogno di tutte le parentesi graffe. Fallo e basta ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel

3

Quando si reindirizza l'output di un comando a un altro (usando |), si reindirizza solo l'output standard. Quindi questo dovrebbe spiegare il perché

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

non produce quello che volevi (funziona però).

Se non si desidera reindirizzare l'output dell'errore sull'output standard, è possibile reindirizzare l'output dell'errore su un file, quindi eseguirlo in un secondo momento

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error

Grazie. it does work, though, vuoi dire che sta funzionando sulla tua macchina? In secondo luogo, come hai sottolineato usando pipe, possiamo solo reindirizzare stdout. Sono interessato ad alcune funzionalità di comando o bash che mi permetteranno di reindirizzare stderr. (ma non il trucco del file temporaneo)
Andrew-Dufresne,

@Andrew Voglio dire, il comando funziona nel modo in cui è stato progettato per funzionare. Semplicemente non funziona nel modo desiderato :)
phunehehe,

Non conosco alcun modo che possa reindirizzare l'output dell'errore di un comando all'input standard di un altro. Sarebbe interessante se qualcuno potesse segnalarlo.
phunehehe,

2

Puoi scambiare i flussi. Ciò consentirebbe grepil flusso di errori standard originale pur ottenendo l'output originariamente passato allo standard output nel terminale:

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

Funziona creando prima un nuovo descrittore di file (3) aperto per l'output e impostandolo sul flusso di errori standard ( 3>&2). Quindi reindirizziamo l'errore standard a output standard ( 2>&1). Infine, l'output standard viene reindirizzato all'errore standard originale e il nuovo descrittore di file viene chiuso ( 1>&3-).

Nel tuo caso:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

Testandolo:

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep "error"
output
error

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep -v "error"
output

1

Mi piace usare la rcshell in questo caso.

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

Questo è un esempio di come scartare stdoute reindirizzare stderrper accedere a 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, la sintassi è semplice, il che rende questa la mia soluzione preferita.

È possibile specificare quale descrittore di file si desidera reindirizzare tra parentesi, subito dopo il carattere pipe.

I descrittori di file standard sono numerati come tali:

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

0

Una variante dell'esempio del sottoprocesso di bash:

eco due righe per stderr e tee stderr in un file, grep tee e pipe out to stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

stdout:

fff

some.load.errors (ad es. stderr):

asdf
fff

-1

prova questo comando

  • crea file con nome casuale
  • invia l'output al file
  • invia il contenuto del file alla pipe
  • grep

ceva -> solo un nome variabile (inglese = qualcosa)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;

Vorrei usare /tmp/$ceva, meglio che sporcare la directory corrente con file temporanei - e stai assumendo che la directory corrente sia scrivibile.
Rolf
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.