¿Cómo uso tee para redirigir a grep?

13

No tengo mucha experiencia en el uso de tee, así que espero que esto no sea muy básico.

Después de ver una de las respuestas a esta pregunta, me encontré con un extraño comportamiento tee.

Para poder generar la primera línea y una línea encontrada, puedo usar esto:

ps aux | tee >(head -n1) | grep syslog
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4

Sin embargo, la primera vez que ejecuté esto (en zsh) el resultado estaba en el orden incorrecto, los encabezados de columna estaban por debajo de los resultados grep (sin embargo, esto no volvió a ocurrir), así que intenté intercambiar los comandos:

ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

¡Solo se imprime la primera línea, y nada más! ¿Puedo usar tee para redirigir a grep, o lo estoy haciendo de manera incorrecta?

Mientras escribía esta pregunta, el segundo comando realmente funcionó una vez para mí, lo ejecuté nuevamente cinco veces y luego volví al resultado de una línea. ¿Es este solo mi sistema? (Estoy ejecutando zsh dentro de tmux).

Finalmente, ¿por qué con el primer comando no se muestra "grep syslog" como resultado (solo hay un resultado)?

Para el control aquí está el grep sin el tee

ps aux | grep syslog
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4
henry    2290  0.0  0.1  95220  3092 ?        Ssl  Sep07   3:12 /usr/bin/pulseaudio --start --log-target=syslog
henry   15924  0.0  0.0   3128   824 pts/4    S+   13:44   0:00 grep syslog

Actualización: Parece que head está causando que todo el comando se trunca (como se indica en la respuesta a continuación), el siguiente comando ahora devuelve lo siguiente:

ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
syslog     806
Rqomey
fuente
No es una respuesta directa a su pregunta, pero sería mucho más limpio hacer algo así ps aux | sed -n -e '1p' -e '/syslog/p'.
jw013
¡Nunca pensé en sed, creo que puede ser una respuesta adecuada para la pregunta relacionada aquí, pero en realidad estoy buscando información sobre el comportamiento inconsistente de estos comandos!
Rqomey

Respuestas:

19
$ ps aux | tee >(head -n1) | grep syslog
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND 
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4

Los comandos grepy headcomienzan aproximadamente al mismo tiempo, y ambos reciben los mismos datos de entrada a su propio ritmo, pero generalmente, a medida que los datos están disponibles. Hay algunas cosas que pueden introducir la salida 'no sincronizada' que cambia las líneas; por ejemplo:

  1. Los datos multiplexados de teerealmente se envían a un proceso antes que al otro, dependiendo principalmente de la implementación de tee. Una teeimplementación simple tendrá readcierta cantidad de entrada, y luego writedos veces: una vez para stdout y otra para su argumento. Esto significa que uno de esos destinos obtendrá primero los datos.

    Sin embargo, todas las tuberías están protegidas. Es probable que estos búferes sean de 1 línea cada uno, pero pueden ser más grandes, lo que puede hacer que uno de los comandos de recepción vea todo lo que necesita para la salida (es decir, la greplínea ped) antes de que el otro comando ( head) haya recibido datos en todos.

  2. A pesar de lo anterior, también es posible que uno de estos comandos reciba los datos pero no pueda hacer nada con ellos a tiempo, y luego el otro comando reciba más datos y los procese rápidamente.

    Por ejemplo, incluso si heady grepson enviados por una línea de datos a la vez, si headno sabe cómo tratar con él (o se retrasa por la programación del núcleo), greppuede mostrar sus resultados antes headincluso tiene la oportunidad. Para demostrarlo, intente agregar un retraso: ps aux | tee >(sleep 1; head -n1) | grep sysloges casi seguro que grepprimero generará la salida.

$ ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

Creo que a menudo solo obtienes una línea aquí, porque headrecibe la primera línea de entrada y luego cierra su stdin y sale. Cuando teeve que su stdout se ha cerrado, cierra su propio stdin (salida de ps) y sale. Esto podría depender de la implementación.

Efectivamente, los únicos datos que psse envían son la primera línea (definitivamente, porque headestá controlando esto), y tal vez algunas otras líneas antes heady teecierren sus descriptores stdin.

La inconsistencia con la aparición de la segunda línea se introduce por el tiempo: headcierra stdin, pero psaún envía datos. Estos dos eventos no están bien sincronizados, por lo que la línea que contiene syslogtodavía tiene la posibilidad de convertirse en teeargumento (el grepcomando). Esto es similar a las explicaciones anteriores.

Puede evitar este problema por completo mediante el uso de comandos que esperan todas las entradas antes de cerrar stdin / salir. Por ejemplo, use en awklugar de head, que leerá y procesará todas sus líneas (incluso si no causan salida):

ps aux | tee >(grep syslog) | awk 'NR == 1'

Pero tenga en cuenta que las líneas aún pueden aparecer fuera de orden, como se indicó anteriormente, lo que se puede demostrar mediante:

ps aux | tee >(grep syslog) | (sleep 1; awk 'NR == 1')

Espero que esto no haya sido demasiado detalle, pero hay muchas cosas simultáneas que interactúan entre sí. Los procesos separados se ejecutan simultáneamente sin ninguna sincronización, por lo que sus acciones en cualquier ejecución particular pueden variar; a veces es útil profundizar en los procesos subyacentes para explicar por qué.

mrb
fuente
1
Excelente respuesta! Realmente pregunté porque estoy interesado en los procesos subyacentes. Cuando las cosas son inconstantes, me parece interesante. ¿Habría una mejor manera de ejecutar ps aux | tee >(grep syslog) | head -n1que dejaría de headcerrar stdout? Wow, este comando ha comenzado a dar salida ahora, pero como sucedería de acuerdo con su respuesta, parece truncadoUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND syslog 806
Rqomey
1
Puede usar algo que no cierre stdin en lugar de head. He actualizado la respuesta con este ejemplo:ps aux | tee >(grep syslog) | awk 'NR == 1'
MRB
1
@KrzysztofAdamski, cuando lo usa >(cmd), el shell crea una tubería con nombre y la pasa como argumento al comando ( tee). Luego teeestá escribiendo en stdout (canalizado a awk) y también en ese argumento. Es lo mismo que mkfifo a_fifo ; grep ... a_fifoen un caparazón y ps | tee a_fifo | awk ...en otro.
mrb
1
@KrzysztofAdamski gnu.org/software/bash/manual/html_node/… - Intenta echo >(exit 0), que hará eco del argumento real pasado por el shell (en mi caso, se convierte /dev/fd/63). Esto debería funcionar igual en bash y zsh.
mrb
1
@mrb: es una característica muy interesante que no conocía antes, gracias. Está funcionando de alguna manera extraña en bash, sin embargo, consulte pastebin.com/xFgRcJdF . Lamentablemente no tengo tiempo para investigar esto ahora, pero lo haré mañana.
Krzysztof Adamski
2

grep syslogno siempre se muestra ya que depende del tiempo. Cuando utiliza la canalización de shell, está ejecutando comandos casi simultáneamente. Pero la clave aquí es la palabra "casi". Si pstermina de escanear todos los procesos antes de iniciar grep, no estará en la lista. Puede obtener resultados aleatorios dependiendo de la carga del sistema, etc.

Algo similar sucede con tu tee. Se ejecuta en segundo plano en subshell y puede dispararse antes o después de grep. Es por eso que el orden de salida es inconsistente.

En cuanto a la pregunta de salida, su comportamiento es bastante extraño. Esto se debe a que no se usa de la manera normal. Se ejecuta sin ningún argumento, lo que significa que solo debe copiar los datos de su stdin a stdout. Pero su stdout se redirige a la cabeza de ejecución del subshell (en el primer caso) o grep (segundo caso). Pero también se canaliza al siguiente comando. Creo que lo que sucede en este caso depende de la implementación. Por ejemplo, en mi bash 4.2.28, nunca se escribe nada en subshell stdin. En zsh, funciona de manera confiable como desea (imprimiendo tanto la primera línea de ps como las líneas buscadas), cada vez que lo intento,

Krzysztof Adamski
fuente
Eso explica una cosa de todos modos, ¡me sorprende que el tee retrase la ejecución de grep en gran medida!
Rqomey
0

Un poco hack, pero aquí está mi solución, en forma de una psgrep()función de shell que uso:

Redireccione la psfila de encabezado a STDERR, luego grepen STDOUT, pero primero elimine el grepcomando en sí, para evitar que la fila de "ruido" surja de grepsí misma:

psgrep() { ps aux | tee >(head -1>&2) | grep -v " grep $@" | grep "$@" -i --color=auto; }
fnl
fuente