tubería, {lista; } solo funciona con algunos programas

13

Necesito explicaciones de los usuarios avanzados para un comportamiento tan impredecible:

ps -eF | { head -n 1;grep worker; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root       441     2  0     0     0   2 paź15 ?       00:00:00 [kworker/2:1H]

todo se ve bien mientras que

ls -la / | { head -n 1;grep sbin; }

muestra solo la salida de head

... Pensé stdout 2>&1y no funciona ni para mí es extraño, ¿alguna explicación o sugerencia de cómo manejarlo?

ast
fuente
1
El último debería imprimir todo. El heady grepno hacer nada allí.
jordanm
Sí, tiene usted razón. Pero en lugar de eso, ¿por qué ps -eF funciona mientras ls -la / not?
ast

Respuestas:

9

Investigué un poco usando stracey parece deberse a la forma en que el programa en el lado izquierdo de la tubería está escribiendo en la terminal. Cuando lsse ejecuta el comando, escribe todos los datos en un solo write(). Esto hace headque se consuma todo el stdin.

Por otro lado, psescribe datos en lotes, por lo que solo el primero write()es consumido por head, y luego existe. Las llamadas posteriores a write()irán al grepproceso recién generado .

Esto significa que no funcionaría si el proceso que está intentando grepno se produjo en el primero write(), ya grepque no puede ver todos los datos (ve incluso menos que solo los datos menos la primera línea).

Aquí hay un ejemplo de cómo intentar grep para pid 1 en mi sistema:

$ ps -eF | { head -n2; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | grep '/lib/systemd/systemd$'
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | { head -n1; grep '/lib/systemd/systemd$'; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD

Tu ps -eFejemplo solo funciona por casualidad.

jordanm
fuente
gran e integral expansión muchas gracias
ast
1
En realidad es más una condición de carrera. Es solo que es más lento realizar múltiples write()llamadas. Si headtardaran en realizar su read()llamada (de modo que el búfer de la tubería tuviera todos los datos), exhibiría el mismo comportamiento en ambos lsy ps.
Patrick
6

Esto es causado por el almacenamiento en búfer en glibc. En el caso de lsla salida está en un búfer interno y como tal se pasa solo a head. Para el ps -eF, la salida es más grande y, por lo tanto, una vez que headfinaliza, lo siguiente grepobtiene las partes restantes de (pero no la totalidad) de la salida ps.

Puede deshacerse de él desarmando la tubería, por ejemplo con sed -u(no estoy seguro de que no sea una extensión GNU):

$ ls -al / | sed -u "#" | { head -n 1; grep bin; }
total 76
drwxr-xr-x   2 root root  4096 Oct  2 21:52 bin
drwxr-xr-x   2 root root  8192 Oct  3 01:54 sbin
Peterph
fuente
4

Lo que sucede es que head -n 1lee más de 1 línea. Para un rendimiento óptimo, la cabeza lee fragmentos de bytes, por lo que podría leer 1024 bytes a la vez, y luego mirar a través de esos bytes para el primer salto de línea. Dado que el salto de línea puede ocurrir en el medio de esos 1024 bytes, se pierde el resto de los datos. No se puede volver a poner en la tubería. Entonces, el siguiente proceso que se ejecuta solo obtiene los bytes 1025 y más.

Su primer comando tiene éxito porque el kworkerproceso es posterior a ese primer fragmento que se headlee.

Para que esto funcione, headtendría que leer 1 carácter a la vez. Pero esto es extremadamente lento, por lo que no lo hace.
La única forma de hacer algo como esto de manera eficiente es hacer que un solo proceso haga tanto la "cabeza" como la "grep".

Aquí hay 2 formas de hacer esto:

echo -e '1\n2\n3\n4\n5' | perl -ne 'print if $i++ == 0 || /4/'

o

echo -e '1\n2\n3\n4\n5' | awk '{if (NR == 1 || /4/) print }'

Hay muchos más ...

Patricio
fuente
sí, he conocido 'way of awk' para manejar esta tarea, pero me preguntaba por qué el comportamiento era tan impredecible con {list; }. Gracias por aclarar cómo funciona. Estoy impresionado con todas las respuestas anteriores
AST
2

Si solo desea la primera o dos líneas, el siguiente tipo de truco funciona y evita los problemas de almacenamiento en búfer causados ​​por el uso de dos comandos diferentes para leer la secuencia de salida:

$ ps -eF   | { IFS= read -r x ; echo "$x" ; grep worker; }
$ ls -la / | { IFS= read -r x ; echo "$x" ; grep sbin; }

El readestá integrado en el shell y no consume un búfer completo de entrada solo para generar una línea, por lo que el uso readdeja todo el resto de la salida para el siguiente comando.

Si desea acentuar los problemas de almacenamiento en búfer que muestran sus ejemplos que usan dos comandos diferentes, agréguelos sleepa ellos para eliminar los problemas de sincronización y permita que el comando de la izquierda genere toda su salida antes de que los comandos de la derecha intenten leer cualquiera de eso:

$ ps -eF   | { sleep 5 ; head -n 1 ; grep worker; }
$ ls -la / | { sleep 5 ; head -n 1 ; grep sbin; }

Ahora, los dos ejemplos anteriores fallan de la misma manera: headlee un búfer completo de la salida solo para producir una línea, y ese búfer no está disponible para lo siguiente grep.

Puede ver el problema del almacenamiento en búfer aún más claramente utilizando algunos ejemplos que numeran las líneas de salida para que pueda saber qué líneas faltan:

$ ps -eF          | cat -n | { sleep 5 ; head -n 1 ; head ; }
$ ls -la /usr/bin | cat -n | { sleep 5 ; head -n 1 ; head ; }

Una forma sencilla de ver el problema del almacenamiento en búfer es utilizar sequn generador de una lista de números. Podemos decir fácilmente qué números faltan:

$ seq 1 100000    | { sleep 5 ; head -n 1 ; head ; }
1

1861
1862
1863
1864
1865
1866
1867
1868
1869

Mi solución truco usando el shell para leer y hacer eco de la primera línea funciona correctamente incluso con el retraso de sueño agregado:

$ seq 1 100000 | { sleep 5 ; IFS= read -r x ; echo "$x" ; head ; }
1
2
3
4
5
6
7
8
9
10
11

A continuación se muestra un ejemplo completo que muestra los headproblemas de almacenamiento en búfer, que muestra cómo headconsume un búfer completo de la salida solo para producir sus cinco líneas cada vez. Ese búfer consumido no está disponible para el siguiente headcomando en la secuencia:

$ seq 1 100000 | { sleep 5 ; head -5 ; head -5 ; head -5 ; head -5 ; }
1
2
3
4
5

1861
1862
1863
1864
499
3500
3501
3502
3503
7
5138
5139
5140
5141

Mirando el número 1861anterior, podemos calcular el tamaño del búfer que se utiliza headcontando la seqsalida de 1a 1860:

$ seq 1 1860 | wc -c
8193

Vemos que headse almacena en búfer leyendo 8 KB completos (8 * 1024 bytes) de la salida de la tubería a la vez, incluso para producir solo unas pocas líneas de su propia salida.

Ian D. Allen
fuente