Canalice solo STDERR a través de un filtro

82

¿Hay alguna forma, en bash, de canalizar STDERR a través de un filtro antes de unificarlo con STDOUT? Es decir, quiero

STDOUT ────────────────┐
                       ├─────> terminal/file/whatever
STDERR ── [ filter ] ──┘

más bien que

STDOUT ────┐
           ├────[ filter ]───> terminal/file/whatever
STDERR ────┘
Martín DeMello
fuente
1
Consulte también ¿Cómo canalizar stderr y no stdout? .
Andrew Marshall

Respuestas:

64

Aquí hay un ejemplo, modelado a partir de cómo intercambiar descriptores de archivos en bash . La salida de a.out es la siguiente, sin el prefijo 'STDXXX:'.

STDERR: stderr output
STDOUT: more regular

./a.out 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'
more regular
stdErr output

Citando el enlace anterior:

  1. Primero guarde stdout como & 3 (& 1 se engaña en 3)
  2. Luego envíe stdout a stderr (& 2 se engaña en 1)
  3. Enviar stderr a & 3 (stdout) (& 3 se convierte en 2)
  4. cerrar & 3 (& - se engaña en 3)
Paul Rubel
fuente
1
No me funciona. Finalmente lo hago funcionar con 3>&2 2>&1 1>&3-.
aleung
3
Precaución : esto supone que FD 3 no está en uso y no deshace el intercambio de los descriptores de archivo 1 y 2, por lo que no puede continuar para canalizar esto a otro comando. Consulte esta respuesta para obtener más detalles y soluciones alternativas. Para una sintaxis mucho más limpia para {ba, z} sh, vea esta respuesta .
Tom Hale
24

Un uso ingenuo de la sustitución de procesos parece permitir el filtrado por stderrseparado de stdout:

:; ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 )
out
e:err

Tenga en cuenta que stderrsale en la parte stderry stdouten la stdoutque podemos ver envolviendo todo el asunto en otro subnivel y redirigir a los archivos oye

( ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 ) ) 1>o 2>e
solidsnack
fuente
porqué el :; ¿al principio? Probé la línea mágica sin, y no parece hacer una diferencia.
Koshmaar
1
Algunas personas lo usan $como símbolo del sistema. Luego se escriben a menudo ejemplos de concha como: $ cat /var/log/syslog | fgrep .... Sin embargo, esta línea no es copiable debido a la extensión $. :;parece un indicador pero es básicamente un shell no-op; para que pueda seleccionar y pegar toda la línea de forma segura.
solidsnack
23
Bien, pero podría omitir:; y la línea también sería copiable :) Para mí:; no parece un mensaje, no aclara el ejemplo (separando los comandos de la salida), pero es confuso. Aunque entiendo su punto de vista y no hablemos de sintaxis / convenciones.
Koshmaar
24

TL; DR:

$ cmd 2> >(stderr-filter >&2)

Ejemplo:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

Esto funcionará tanto en bash como en zsh. Bash es bastante omnipresente en estos días, sin embargo, si realmente necesita una solución (realmente retorcida) para POSIX sh, consulte aquí .


Explicación

De lejos, la forma más fácil de hacer esto es redirigir STDERR mediante la sustitución de procesos :

La sustitución de procesos permite hacer referencia a la entrada o salida de un proceso mediante un nombre de archivo. Toma la forma de

>(list)

La lista de procesos se ejecuta de forma asincrónica y su entrada o salida aparece como un nombre de archivo.

Entonces, lo que obtienes con la sustitución de procesos es un nombre de archivo.

Como podrías hacer:

$ cmd 2> filename

tu puedes hacer

$ cmd 2> >(filter >&2)

El STDOUT de >&2la redirección filtervuelve al STDERR original.

Tom Hale
fuente
1
Hay una advertencia que acompaña a esta respuesta: bash no espera a que se complete el proceso sustituido , mientras que los malabarismos FD para intercambiar 1 y 2 sí lo hacen. Esto puede ser importante. Me quemé al hacerlo exec 2> >(while .. read .. echo)en un script que se ejecuta como un servicio systemd. journald captura fd2 del servicio e infiere el nivel de registro de un prefijo '<N>': anteponer <2> a las líneas de salida, y obtiene el nivel de ERROR asignado a los registros de diario. Pero a veces systemd fue más rápido para matar a todo el grupo de procesos al salir del principal antes de que pudiera registrar su último, ¡el mensaje más importante!
kkm
Buena solución simple. Advertencia: tenga cuidado al usar esto como parte de un trabajo cron; la sustitución de procesos no está disponible en algunos shells utilizados por cron.
Quinn acusado
15

TL; DR: (bash y zsh)

$ cmd 2> >(stderr-filter >&2)

Ejemplo:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

Muchas respuestas en la red StackExchange tienen la forma:

cat /non-existant 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'

Esto tiene una suposición incorporada: que el descriptor de archivo 3 no se usa para otra cosa.

En su lugar, utilice un descriptor de archivo con nombre y {ba,z}shasignará el siguiente descriptor de archivo disponible> = 10:

cat /non-existant {tmp}>&1 1>&2 2>&$tmp {tmp}>&- | sed 's/e/E/g'

Tenga en cuenta que POSIX no admite descriptores de archivos con nombre sh.

El otro problema con lo anterior es que el comando no se puede canalizar a otros comandos sin volver a intercambiar STDOUT y STDERR a sus valores originales.

Para permitir la tubería en adelante en POSIX sh, (y aún asumiendo que FD 3 no se usa) se vuelve complicado :

(cmd 2>&1 >&3 3>&- | stderr-filter >&2 3>&-) 3>&1

Entonces, dada la suposición y la sintaxis retorcida de esto, es probable que sea mejor usar la sintaxis bash/ más simple que se zshmuestra en el TL; DR anterior y se explica aquí .

Tom Hale
fuente
8

Encuentro el uso de la sustitución del proceso bash más fácil de recordar y usar ya que refleja la intención original casi literalmente. Por ejemplo:

$ cat ./p
echo stdout
echo stderr >&2
$ ./p 2> >(sed -e 's/s/S/') | sed 's/t/T/'
sTdout
STderr

utiliza el primer comando sed como filtro solo en stderr y el segundo comando sed para modificar la salida unida.

Tenga en cuenta que el espacio en blanco después de 2> es obligatorio para que el comando se analice correctamente.

Pecunifex
fuente
5

La última parte de esta página de Advanced Bash Scripting Guide es "redireccionar solo stderr a una tubería".

# Redirigiendo solo stderr a una tubería.

exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

# Gracias, SC

Esto puede ser lo que quieras. Si no, alguna otra parte de la ABSG debería poder ayudarte, es excelente.

signine
fuente
Dudamos un poco en recomendar la ABSG como referencia, ya que mezcla documentación, prescripción y opinión sin marcar claramente las diferencias. Algunas secciones también tienen contenido dudoso, aunque la que enlaza parece estar bien.
tripleee
3

Eche un vistazo a las tuberías con nombre:

$ mkfifo err
$ cmd1 2>err |cat - err |cmd2
Wilhelmtell
fuente
no cat - errromperá la intercalación de stdout y stderr?
Martin DeMello
@Martin - depende. Si cmd1, cat o cmd2 almacenan la salida en búfer, entonces podría ver la salida fuera de secuencia.
mob