¿Cómo puedo canalizar stderr y no stdout?

982

Tengo un programa que escribe información en stdouty stderr, y necesito hacerlo a greptravés de lo que viene a stderr , sin tener en cuenta stdout .

Por supuesto que puedo hacerlo en 2 pasos:

command > /dev/null 2> temp.file
grep 'something' temp.file

pero preferiría poder hacer esto sin archivos temporales. ¿Hay algún truco de tubería inteligente?

Peter Mortensen
fuente
Una pregunta similar, pero conservando stdout: unix.stackexchange.com/questions/3514/…
joeytwiddle
Esta pregunta fue para Bash, pero vale la pena mencionar este artículo relacionado para el shell Bourne / Almquist.
Stephen Niedzielski
10
Me esperaba algo como esto: command 2| othercommand. Bash es tan perfecto que el desarrollo terminó en 1982, así que nunca lo veremos en bash, me temo.
Rolf

Respuestas:

1190

Primero redirige stderr a stdout: la tubería; luego redirija stdout a /dev/null(sin cambiar a dónde va stderr):

command 2>&1 >/dev/null | grep 'something'

Para obtener detalles sobre la redirección de E / S en toda su variedad, consulte el capítulo sobre redirecciones en el manual de referencia de Bash.

Tenga en cuenta que la secuencia de redireccionamientos de E / S se interpreta de izquierda a derecha, pero las tuberías se configuran antes de que se interpreten las redirecciones de E / S. Los descriptores de archivos como 1 y 2 son referencias a descripciones de archivos abiertos. La operación 2>&1hace que el descriptor de archivo 2 también conocido como stderr se refiera a la misma descripción de archivo abierto que el descriptor de archivo 1 al que stdout se refiere actualmente (ver dup2()y open()). La operación >/dev/nullluego cambia el descriptor de archivo 1 para que se refiera a una descripción de archivo abierto /dev/null, pero eso no cambia el hecho de que el descriptor de archivo 2 se refiere a la descripción de archivo abierto a la que el descriptor de archivo 1 apuntaba originalmente, es decir, la tubería.

Jonathan Leffler
fuente
44
Me encontré con / dev / stdout / dev / stderr / dev / stdin el otro día, y tenía curiosidad por saber si esas son buenas formas de hacer lo mismo. Siempre pensé que 2> & 1 estaba un poco ofuscado. Entonces algo como: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons
17
Podrías usar /dev/stdoutet al, o usar /dev/fd/N. Serán marginalmente menos eficientes a menos que el shell los trate como casos especiales; la notación numérica pura no implica acceder a los archivos por nombre, pero el uso de los dispositivos significa una búsqueda de nombre de archivo. Si se puede medir eso es discutible. Me gusta la brevedad de la notación numérica, pero la he estado usando durante tanto tiempo (más de un cuarto de siglo; ¡ay!) Que no estoy calificado para juzgar sus méritos en el mundo moderno.
Jonathan Leffler
23
@Jonathan Leffler: Tengo un pequeño problema con su explicación de texto sin formato 'Redirigir stderr a stdout y luego stdout a / dev / null' - Dado que uno tiene que leer las cadenas de redireccionamiento de derecha a izquierda (no de izquierda a derecha), también debería adaptar nuestra explicación de texto plano a esto: 'Redirigir stdout a / dev / null, y luego stderr a donde solía estar stdout' .
Kurt Pfeifle 01 de
116
@KurtPfeifle: au contraire! Uno debe leer las cadenas de redireccionamiento de izquierda a derecha, ya que esa es la forma en que el shell las procesa. La primera operación es 2>&1, lo que significa 'conectar stderr al descriptor de archivo al que va stdout actualmente '. La segunda operación es 'cambiar stdout para que vaya a /dev/null', dejando stderr yendo al stdout original, la tubería. El caparazón divide las cosas en el símbolo de la tubería primero, por lo tanto, la redirección de la tubería ocurre antes de las redirecciones 2>&1o >/dev/null, pero eso es todo; Las otras operaciones son de izquierda a derecha. (De derecha a izquierda no funcionaría)
Jonathan Leffler
14
Lo que realmente me sorprende de esto es que también funciona en Windows (después de cambiar /dev/nullel nombre al equivalente de Windows nul).
Michael Burr
364

O para intercambiar la salida del error estándar y la salida estándar, use:

command 3>&1 1>&2 2>&3

Esto crea un nuevo descriptor de archivo (3) y lo asigna al mismo lugar que 1 (salida estándar), luego asigna fd 1 (salida estándar) al mismo lugar que fd 2 (error estándar) y finalmente asigna fd 2 (error estándar ) al mismo lugar que fd 3 (salida estándar).

El error estándar ahora está disponible como salida estándar y la salida estándar anterior se conserva en error estándar. Esto puede ser excesivo, pero esperamos que brinde más detalles sobre los descriptores de archivos Bash (hay nueve disponibles para cada proceso).

Kramish
fuente
100
Un ajuste final sería 3>&-cerrar el descriptor de repuesto que creó desde stdout
Jonathan Leffler
1
¿Podemos crear un descriptor de archivo que tenga stderry otro que tenga la combinación de stderry stdout? En otras palabras, ¿pueden stderrir a dos archivos diferentes a la vez?
Stuart
Lo siguiente aún imprime errores en stdout. ¿Qué me estoy perdiendo? ls -l not_a_file 3> & 1 1> & 2 2> & 3> errors.txt
user48956
1
@ JonasDahlbæk: el ajuste es principalmente una cuestión de orden. En situaciones verdaderamente arcanas, puede hacer la diferencia entre un proceso de detección y no detección de EOF, pero eso requiere circunstancias muy peculiares.
Jonathan Leffler
1
Precaución : esto supone que FD 3 aún no está en uso, no lo cierra y no deshace el intercambio de los descriptores de archivo 1 y 2, por lo que no puede pasar esto a otro comando. Consulte esta respuesta para obtener más detalles y soluciones. Para una sintaxis mucho más limpia para {ba, z} sh, vea esta respuesta .
Tom Hale
218

En Bash, también puede redirigir a una subshell utilizando la sustitución de procesos :

command > >(stdlog pipe)  2> >(stderr pipe)

Para el caso en cuestión:

command 2> >(grep 'something') >/dev/null
Rich Johnson
fuente
1
Funciona muy bien para la salida a la pantalla. ¿Tiene alguna idea de por qué el contenido no agrupado aparece nuevamente si redirijo la salida grep a un archivo? Después de command 2> >(grep 'something' > grep.log)grep.log contiene el mismo resultado que ungrepped.log decommand 2> ungrepped.log
Tim
99
Uso 2> >(stderr pipe >&2). De lo contrario, la salida del "tubo stderr" pasará por el "tubo stdlog".
ceving
¡sí ! 2> >(...), funciona, lo intenté 2>&1 > >(...)pero no fue así
datdinhquoc
Aquí hay un pequeño ejemplo que puede ayudarme la próxima vez que busque cómo hacer esto. Considere lo siguiente ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) En este caso, también quería ver lo que salía como errores en mi consola. Pero STDOUT iba al archivo de salida. Entonces, dentro del subconjunto, debe redirigir ese STDOUT de nuevo a STDERR dentro de los paréntesis. Mientras eso funciona, la salida STDOUT del teecomando termina al final del out-content.txtarchivo. Eso me parece inconsistente.
será el
@datdinhquoc Lo hice de alguna manera como2>&1 1> >(dest pipe)
Alireza Mohamadi
195

Combinando la mejor de estas respuestas, si lo haces:

command 2> >(grep -v something 1>&2)

... entonces todo stdout se conserva como stdout y todo stderr se conserva como stderr, pero no verá ninguna línea en stderr que contenga la cadena "algo".

Esto tiene la ventaja única de no revertir o descartar stdout y stderr, ni eliminarlos juntos, ni usar ningún archivo temporal.

Pinko
fuente
¿No es command 2> >(grep -v something)(sin 1>&2) lo mismo?
Francesc Rosas
11
No, sin eso, el stderr filtrado termina siendo enrutado a stdout.
Pinko
1
Esto es lo que necesitaba: las salidas tar "cambian el archivo a medida que lo leemos" para un directorio siempre, así que solo quiero filtrar esa línea pero ver si se producen otros errores. Entonces tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)debería funcionar.
Razzed
es incorrecto decir "comenzando con la cadena". No hay nada sobre la sintaxis presentada de grep que hará que solo excluya líneas que comienzan con la cadena dada. Las líneas se excluirán si contienen la cadena dada en alguna parte de ellas.
Mike Nakis
@ MikeNakis gracias - arreglado! (Eso fue sobrante de mi borrador de respuesta original, en el que tenía sentido ...)
Pinko
102

Es mucho más fácil visualizar las cosas si piensas en lo que realmente está sucediendo con los "redireccionamientos" y las "canalizaciones". Los redireccionamientos y las canalizaciones en bash hacen una cosa: modificar dónde apuntan los descriptores de archivo de proceso 0, 1 y 2 (ver / proc / [pid] / fd / *).

Cuando una tubería o "|" El operador está presente en la línea de comando, lo primero que sucede es que bash crea un fifo y señala el FD 1 del comando del lado izquierdo a este fifo, y apunta el FD 0 del comando del lado derecho al mismo fifo.

A continuación, los operadores de redireccionamiento para cada lado se evalúan de izquierda a derecha , y la configuración actual se utiliza cada vez que se produce la duplicación del descriptor. Esto es importante porque, dado que la tubería se configuró primero, el FD1 (lado izquierdo) y el FD0 (lado derecho) ya han cambiado de lo que podrían haber sido normalmente, y cualquier duplicación de estos reflejará ese hecho.

Por lo tanto, cuando escribe algo como lo siguiente:

command 2>&1 >/dev/null | grep 'something'

Esto es lo que sucede, en orden:

  1. se crea una tubería (fifo). El "comando FD1" apunta a esta tubería. "grep FD0" también apunta a esta tubería
  2. "comando FD2" apunta a donde "comando FD1" apunta actualmente (la tubería)
  3. "comando FD1" apunta a / dev / null

Entonces, todos los resultados que el "comando" escribe en su FD 2 (stderr) se dirigen a la tubería y son leídos por "grep" en el otro lado. Todos los resultados que el "comando" escribe en su FD 1 (stdout) llegan a / dev / null.

Si, en cambio, ejecuta lo siguiente:

command >/dev/null 2>&1 | grep 'something'

Esto es lo que pasa:

  1. se crea una tubería y se le apunta "comando FD 1" y "grep FD 0"
  2. "comando FD 1" apunta a / dev / null
  3. "comando FD 2" apunta a dónde apunta FD 1 actualmente (/ dev / null)

Entonces, todos los stdout y stderr del "comando" van a / dev / null. Nada va a la tubería y, por lo tanto, "grep" se cerrará sin mostrar nada en la pantalla.

También tenga en cuenta que las redirecciones (descriptores de archivo) pueden ser de solo lectura (<), de solo escritura (>) o de lectura-escritura (<>).

Una nota final. Si un programa escribe algo en FD1 o FD2, depende completamente del programador. La buena práctica de programación dicta que los mensajes de error deben ir a FD 2 y la salida normal a FD 1, pero a menudo encontrará una programación descuidada que mezcla los dos o ignora la convención.

Michael Martinez
fuente
66
Muy buena respuesta. Mi única sugerencia sería reemplazar su primer uso de "fifo" con "fifo (una tubería con nombre)". He estado usando Linux por un tiempo, pero de alguna manera nunca logré aprender que es otro término para canalización con nombre. Esto me habría salvado de buscarlo, pero, de nuevo, ¡no habría aprendido las otras cosas que vi cuando lo descubrí!
Mark Edington
3
@ MarkEdington Tenga en cuenta que FIFO es solo otro término para tubería con nombre en el contexto de tuberías e IPC . En un contexto más general, FIFO significa Primero en entrar, primero en salir, que describe la inserción y eliminación de una estructura de datos en cola.
Loomchild
55
@Loomchild Por supuesto. El punto de mi comentario fue que, incluso como desarrollador experimentado, nunca había visto a FIFO utilizado como sinónimo de canalización con nombre. En otras palabras, no sabía esto: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Aclarando que en la respuesta me habría ahorrado tiempo.
Mark Edington el
39

Si está usando Bash, entonces use:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines

Ken Sharp
fuente
44
No, |&es igual a 2>&1que combina stdout y stderr. La pregunta solicitó explícitamente la salida sin stdout.
Profpatsch
3
„Si se usa '| &', el error estándar del comando1 se conecta a la entrada estándar del comando2 a través de la tubería; es la abreviatura de 2> y 1 | " Tomado literalmente del cuarto párrafo en su enlace.
Profpatsch
99
@Profpatsch: la respuesta de Ken es correcta, mira que redirige stdout a nulo antes de combinar stdout y stderr, por lo que entrará en la tubería solo el stderr, porque stdout anteriormente se cayó a / dev / null.
Luciano
3
Pero todavía encontré que su respuesta es incorrecta, >/dev/null |&expanda >/dev/null 2>&1 | y significa que el inodo stdout está vacío para canalizar porque nadie (# 1 # 2 tanto vinculado a / dev / inodo nulo) está vinculado al inodo stdout (por ejemplo ls -R /tmp/* >/dev/null 2>&1 | grep i, dará vacío, pero ls -R /tmp/* 2>&1 >/dev/null | grep idejará # 2 que atado al inodo estándar se canalizará).
Fruta
3
Ken Sharp, probé, y ( echo out; echo err >&2 ) >/dev/null |& grep "."no da salida (donde queremos "err"). man bashdice If | & se usa ... es la abreviatura de 2> & 1 |. Esta redirección implícita del error estándar a la salida estándar se realiza después de cualquier redirección especificada por el comando. Entonces, primero redirigimos el comando FD1 a nulo, luego redirigimos el comando FD2 a donde FD1 apuntó, es decir. nulo, por lo que grep's FD0 no recibe ninguna entrada. Consulte stackoverflow.com/a/18342079/69663 para obtener una explicación más detallada.
martilleo
11

Para aquellos que desean redirigir stdout y stderr permanentemente a archivos, grep en stderr, pero mantén el stdout para escribir mensajes en un tty:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3
JBD
fuente
6

Esto redirigirá command1 stderr a command2 stdin, mientras deja command1 stdout como está.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Tomado de LDP

el delfin
fuente
2

Acabo de encontrar una solución para enviar stdouta un comando y stderra otro, utilizando tuberías con nombre.

Aquí va.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

Probablemente sea una buena idea eliminar las tuberías con nombre después.

Cinética de Tripp
fuente
0

Puede usar el shell rc .

Primero instale el paquete (es menos de 1 MB).

Este un ejemplo de cómo se deseche la salida estándar y el error estándar de tubo de grep en 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á notado, puede especificar qué descriptor de archivo desea canalizar utilizando corchetes después de la 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
-3

Trato de seguir, encuentro que funciona también,

command > /dev/null 2>&1 | grep 'something'
lasteye
fuente
No funciona Simplemente envía stderr a la terminal. Ignora la tubería.
Tripp Kinetics