¿Cómo grep flujo de error estándar (stderr)?

76

Estoy usando ffmpeg para obtener la metainformación de un clip de audio. Pero no puedo entenderlo.

    $ 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 
      ...

Verifiqué, esta salida de ffmpeg está dirigida a stderr.

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

Así que creo que grep no puede leer la secuencia de errores para detectar líneas coincidentes. ¿Cómo podemos habilitar grep para leer la secuencia de error?

Usando el enlace nixCraft , redirigí el flujo de error estándar al flujo de salida estándar, luego grep funcionó.

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

Pero, ¿qué pasa si no queremos redirigir stderr a stdout?

Andrew-Dufresne
fuente
1
Creo que grepeso solo puede funcionar en stdout (aunque no puedo encontrar la fuente canónica para respaldar eso), lo que significa que cualquier flujo debe convertirse primero en stdout.
Stefan Lasiewski
9
@Stefan: grepsolo puede funcionar con stdin. Es la tubería creada por el shell que conecta el stdin de grep con el stdout del otro comando. Y el shell solo puede conectar un stdout a un stdin.
Gilles 'SO- deja de ser malvado'
Vaya, tienes razón. Creo que eso es lo que realmente quise decir, simplemente no lo pensé bien. Gracias @Giles.
Stefan Lasiewski
¿Quieres que todavía imprima stdout?
Mikel

Respuestas:

52

Si está utilizando bashpor qué no emplear tuberías anónimas, en esencia, abreviatura de lo que dijo phunehehe:

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

Jé Queue
fuente
3
+1 Nice! Bash solo, pero mucho más limpio que las alternativas.
Mikel
1
+1 cpcp -r dir* to 2> >(grep -v "svn")
Usé
55
Si desea que la salida redirigida filtrada en stderr nuevamente, agregue un >&2, por ejemplo,command 2> >(grep something >&2)
tlo
2
Por favor explique cómo funciona esto. 2>redirige stderr a archivo, lo entiendo. Esto se lee del archivo >(grep -i Duration). Pero el archivo nunca se almacena? ¿Cómo se llama esta técnica para poder leer más al respecto?
Marko Avlijaš
1
Es "azúcar sintáctico" para crear y una tubería (no un archivo) y cuando se complete eliminar esa tubería. Son efectivamente anónimos porque no se les asigna un nombre en el sistema de archivos. Bash llama a este proceso de sustitución.
Jé Queue
48

Ninguno de los shells habituales (incluso zsh) permite tuberías que no sean stdout a stdin. Pero todos los shells de estilo Bourne admiten la reasignación de descriptores de archivo (como en 1>&2). Por lo tanto, puede desviar temporalmente stdout a fd 3 y stderr a stdout, y luego colocar fd 3 nuevamente en stdout. Si stuffproduce alguna salida en stdout y otra salida en stderr, y desea aplicar filteren la salida de error dejando intacta la salida estándar, puede usarla { 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
Gilles 'SO- deja de ser malvado'
fuente
Este enfoque funciona. Desafortunadamente, en mi caso, si se devuelve un valor de retorno distinto de cero, se pierde; el valor devuelto es 0 para mí. Puede que esto no siempre suceda, pero sucede en el caso que estoy viendo actualmente. ¿Hay alguna forma de guardarlo?
Faheem Mitha
2
@FaheemMitha No estoy seguro de lo que estás haciendo, pero tal vez pipestatusayudaría
Gilles 'SO- deja de ser malvado'
1
@FaheemMitha, también set -o pipefailpuede ser útil aquí, dependiendo de lo que desee hacer con el estado del error. (Por ejemplo, si ha set -eactivado para fallar en algún error, probablemente set -o pipefailtambién desee ).
Comodín el
@Wildcard Sí, me he set -eactivado para fallar en cualquier error.
Faheem Mitha
La rccarcasa permite tuberías stderr. Vea mi respuesta a continuación.
Rolf
20

Esto es similar al "truco del archivo temporal" de phunehehe, pero en su lugar utiliza una canalización con nombre, lo que le permite obtener resultados un poco más cercanos a cuando salen, lo que puede ser útil para comandos de ejecución prolongada:

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

En esta construcción, stderr se dirigirá a la tubería llamada "mypipe". Como grepse ha llamado con un argumento de archivo, no buscará en STDIN su entrada. Desafortunadamente, aún tendrá que limpiar esa tubería con nombre una vez que haya terminado.

Si está utilizando Bash 4, hay una sintaxis de acceso directo para command1 2>&1 | command2, que es command1 |& command2. Sin embargo, creo que esto es puramente un acceso directo de sintaxis, todavía está redirigiendo STDERR a STDOUT.

Steven D
fuente
Relacionado: stackoverflow.com/questions/2871233/…
Stefan Lasiewski
1
Esa |&sintaxis es perfecta para limpiar rastros hinchados de pila de Ruby stderr. Finalmente puedo agarrarlos sin demasiados problemas.
pgr
8

Las respuestas de Gilles y Stefan Lasiewski son buenas, pero de esta manera es más simple:

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

Supongo que no quieres ffmpeg'simprimir stdout.

Cómo funciona:

  • tuberías primero
    • Se inician ffmpeg y grep, con stdout de ffmpeg yendo a stdin de grep
  • redirecciones a continuación, de izquierda a derecha
    • El stderr de ffmpeg se establece en cualquiera que sea su stdout (actualmente la tubería)
    • La salida estándar de ffmpeg está establecida en / dev / null
Mikel
fuente
Esta descripción es confusa para mí. Las últimas dos viñetas me hacen pensar "redirigir stderr a stdout", luego "redirigir stdout (con stderr, ahora) a / dev / null". Sin embargo, esto no es lo que realmente está haciendo. Esas declaraciones parecen revertirse.
Steve
1
@ Steve No hay "con stderr" en la segunda viñeta. ¿Has visto unix.stackexchange.com/questions/37660/order-of-redirections ?
Mikel
No, quiero decir que esa es mi interpretación de cómo lo describiste en inglés. Sin embargo, el enlace que proporcionó es muy útil.
Steve
¿Por qué era necesario ser redirigido a / dev / null?
Kanwaljeet Singh
7

Vea a continuación el script utilizado en estas pruebas.

Grep solo puede operar en stdin, por lo tanto, debe convertir la secuencia stderr en una forma que Grep pueda analizar.

Normalmente, stdout y stderr se imprimen en su pantalla:

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

Para ocultar stdout, pero aún imprimir stderr, haga esto:

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

¡Pero grep no funcionará en stderr! Es de esperar que el siguiente comando suprima las líneas que contienen 'err', pero no lo hace.

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

Aquí está la solución.

La siguiente sintaxis de Bash ocultará la salida a stdout, pero seguirá mostrando stderr. Primero canalizamos stdout a / dev / null, luego convertimos stderr a stdout, porque las tuberías Unix solo funcionarán en stdout. Todavía puedes grep el texto.

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

(Tenga en cuenta que el comando anterior es diferente a continuación ./command >/dev/null 2>&1, que es un comando muy común).

Aquí está la secuencia de comandos utilizada para las pruebas. Esto imprime una línea en stdout y una línea en 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
Stefan Lasiewski
fuente
Si cambia las redirecciones, no necesita todas las llaves. Solo hazlo ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel
3

Cuando canaliza la salida de un comando a otro (usando |), solo está redirigiendo la salida estándar. Entonces eso debería explicar por qué

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

no emite lo que quería (sin embargo, funciona).

Si no desea redirigir la salida de error a la salida estándar, puede redirigir la salida de error a un archivo, luego grep más tarde

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
phunehehe
fuente
Gracias. it does work, though¿Quieres decir que está funcionando en tu máquina? En segundo lugar, como señaló con el uso de tubería, solo podemos redirigir stdout. Estoy interesado en algún comando o función bash que me permita redirigir stderr. (pero no el truco del archivo temporal)
Andrew-Dufresne
@ Andrew Quiero decir, el comando funciona de la forma en que ha sido diseñado para funcionar. Simplemente no funciona de la manera que quieres :)
phunehehe
No sé de ninguna manera que pueda redirigir la salida de error de un comando a la entrada estándar de otro. Sería interesante si alguien puede señalar eso.
phunehehe
2

Puede intercambiar las transmisiones. Esto le permitiría grepobtener el flujo de error estándar original mientras obtiene la salida que originalmente fue a la salida estándar en el terminal:

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

Esto funciona creando primero un nuevo descriptor de archivo (3) abierto para la salida y configurándolo en la secuencia de error estándar ( 3>&2). Luego redirigimos el error estándar a la salida estándar ( 2>&1). Finalmente, la salida estándar se redirige al error estándar original y se cierra el nuevo descriptor de archivo ( 1>&3-).

En tu caso:

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

Probándolo:

$ ( 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
Kusalananda
fuente
1

Me gusta usar el rcshell en este caso.

Primero instale el paquete (es inferior a 1 MB).

Este es un ejemplo de cómo descartaría stdouty canalizaría stderrgrep rc:

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

Puedes hacerlo sin salir de Bash:

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

Como habrás notado, la sintaxis es sencilla, lo que hace que esta sea mi solución preferida.

Puede especificar qué descriptor de archivo desea canalizar entre paréntesis, justo después del carácter de canalización.

Los descriptores de archivo estándar están numerados como tales:

  • 0: entrada estándar
  • 1: salida estándar
  • 2: error estándar
Rolf
fuente
0

Una variación en el ejemplo de subproceso bash:

haga eco de dos líneas para stderr y tee stderr a un archivo, coloque el tee y la tubería de nuevo a stdout

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

stdout:

fff

some.load.errors (por ejemplo, stderr):

asdf
fff
jmunsch
fuente
-1

prueba este comando

  • crear archivo con nombre aleatorio
  • enviar salida al archivo
  • enviar contenido del archivo a la tubería
  • grep

ceva -> solo un nombre de variable (inglés = algo)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;
Rodislav Moldovan
fuente
Lo usaría /tmp/$ceva, mejor que ensuciar el directorio actual con archivos temporales, y está asumiendo que el directorio actual es editable.
Rolf