it-swarm-es.tech

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

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?

90
Andrew-Dufresne

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

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

66
Jé Queue

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 volver a colocar fd 3 en stdout. Si stuff produce alguna salida en stdout y otra salida en stderr, y desea aplicar filter en la salida de error dejando intacta la salida estándar, puede usar { 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

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 grep ha sido 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, cual es command1 |& command2. Sin embargo, creo que esto es puramente un acceso directo de sintaxis, todavía está redirigiendo STDERR a STDOUT.

22
Steven D

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"

Asumo que no quieres ffmpeg's stdout impreso.

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

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 aún mostrará 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 luego ./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
8
Stefan Lasiewski

Puede intercambiar las transmisiones. Esto le permitiría grep la secuencia 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
3
Kusalananda

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
3
phunehehe

bash puede redirigir stdout a la secuencia stdin a través de una tubería normal - | También puede redirigir stdout y stderr a stdin por |&

1
0x00

Me gusta usar el rc Shell en este caso.

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

Este es un ejemplo de cómo descartaría stdout y pipe stderr para grep 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
1
Rolf

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