it-swarm.it

Come grep flusso di errori standard (stderr)?

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 nixCraft link, ho reindirizzato il flusso di errore 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?

90
Andrew-Dufresne

Se stai usando bash perché non usi pipe anonime, in sostanza abbreviazione di ciò che phunehehe ha detto:

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

66
Jé Queue

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 stuff produce dell'output su stdout e dell'output su stderr e si desidera applicare filter sull'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 è 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à in STDIN 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 questo sia puramente un collegamento alla sintassi, stai ancora reindirizzando STDERR a STDOUT.

22
Steven D

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's stdout stampato.

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
11
Mikel

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

(Nota 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
8
Stefan Lasiewski

Puoi scambiare i flussi. Ciò consentirebbe di grep il 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 all'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
3
Kusalananda

Quando si reindirizza l'output di un comando a un altro (usando |), stai solo reindirizzando 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
3
phunehehe

bash può reindirizzare stdout al flusso stdin tramite la normale pipe - | Può anche reindirizzare sia stdout che stderr su stdin di |&

1
0x00

Mi piace usare la shell rc in questo caso.

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

Questo è un esempio di come scarterai stdout e pipe stderr 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, 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
1
Rolf

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
0
jmunsch