¿En qué orden se ejecutan los comandos canalizados?

89

Nunca he pensado realmente cómo el shell realmente ejecuta comandos canalizados. Siempre me han dicho que el "stdout de un programa se canaliza al stdin de otro", como una forma de pensar en las tuberías. Entonces, naturalmente, pensé que en el caso de decir, A | B, A correría primero, luego B obtiene el stdout de A y usa el stdout de A como su entrada.

Pero he notado que cuando las personas buscan un proceso en particular en ps, incluyen grep -v "grep" al final del comando para asegurarse de que grep no aparezca en el resultado final. Esto significa que en el comando ps aux | grep "bash" | grep -v "grep", lo que significa que ps sabía que grep se estaba ejecutando y, por lo tanto, está en la salida de ps. Pero si ps termina de ejecutarse antes de que su salida se canalice a grep, ¿cómo sabía que grep se estaba ejecutando?

flamingtoast@FTOAST-UBUNTU: ~$ ps | grep ".*"
PID TTY          TIME CMD
3773 pts/0    00:00:00 bash
3784 pts/0    00:00:00 ps
3785 pts/0    00:00:00 grep
acción_patata
fuente
¿Por qué no aceptar una respuesta?
törzsmókus

Respuestas:

64

Los comandos canalizados se ejecutan simultáneamente. Cuando corres ps | grep …, es la suerte del sorteo (o una cuestión de detalles del funcionamiento del shell combinado con el ajuste del programador en las entrañas del núcleo) si se inicia pso no grep, y en cualquier caso continúan. ejecutar simultáneamente

Esto se usa muy comúnmente para permitir que el segundo programa procese datos a medida que salen del primer programa, antes de que el primer programa haya completado su operación. Por ejemplo

grep pattern very-large-file | tr a-z A-Z

comienza a mostrar las líneas coincidentes en mayúsculas incluso antes de grepterminar de recorrer el archivo grande.

grep pattern very-large-file | head -n 1

muestra la primera línea coincidente y puede detener el procesamiento mucho antes de que grephaya terminado de leer su archivo de entrada.

Si leyó en alguna parte que los programas canalizados se ejecutan en secuencia, huya de este documento. Los programas canalizados se ejecutan simultáneamente y siempre lo han hecho.

Gilles
fuente
77
Y lo bueno de este ejemplo es que cuando head obtiene la línea que necesita, termina y cuando grep se da cuenta de esto, también termina sin hacer un montón de trabajo adicional por nada.
Joe
Supongo que hay algún tipo de búfer de E / S con respecto a la tubería ... ¿cómo sé que es el tamaño en bytes? ¿Qué quiero leer para obtener más información al respecto? :)
n611x007
3
@naxa Hay dos buffers, en realidad. Existe el búfer stdio dentro del grepprograma, y ​​hay un búfer administrado por el núcleo en la tubería. Para el último, vea ¿Qué tan grande es el buffer de la tubería?
Gilles
49

El orden en que se ejecutan los comandos en realidad no importa y no está garantizado. Dejando de lado los detalles arcanos de pipe(), fork(), dup()y execve(), la cáscara primero crea la tubería, el conducto para los datos que fluirá entre los procesos y, a continuación, crea los procesos con los extremos de la tubería conectada a ellos. El primer proceso que se ejecuta puede bloquear la espera de entrada del segundo proceso, o bloquear la espera de que el segundo proceso comience a leer datos de la tubería. Estas esperas pueden ser arbitrariamente largas y no importan. Independientemente del orden en que se ejecuten los procesos, los datos eventualmente se transfieren y todo funciona.

Kyle Jones
fuente
55
Buena respuesta, pero el OP parece pensar que los procesos se ejecutan secuencialmente. Puede aclarar aquí que los procesos se ejecutan simultáneamente y que la tubería es como ... una tubería entre cubetas, donde el agua fluye a través de todo al mismo tiempo (aproximadamente).
Keith
Gracias por la aclaración. Las fuentes que he estado leyendo hacen que parezca que los programas canalizados se ejecutan secuencialmente, en lugar de concurrentemente.
action_potato
Para ver la experiencia de los procesos que comienzan de manera indeterminada, intente ejecutar esto 1000 veces: echo -na> & 2 | echo b> & 2
Ole Tange
28

A riesgo de vencer a un caballo muerto, la idea errónea parece ser que

    A | si

es equivalente a

    A > archivo_temporal 
    B < archivo_temporal 
    rm archivo_temporal

Pero, cuando se creó Unix y los niños montaron dinosaurios para ir a la escuela, los discos eran muy pequeños y era común que un comando bastante benigno consumiera todo el espacio libre en un sistema de archivos. Si Bfuera algo así , el resultado final de la tubería podría ser mucho más pequeño que ese archivo intermedio. Por lo tanto, se desarrolló la tubería, no como una abreviatura para el “ejecutar A primero, y luego ejecutar B con el aporte de A ‘s de salida”modelo, pero como una manera para ejecutar simultáneamente con y eliminar la necesidad de almacenar el archivo intermedio en el discogrep some_very_obscure_stringBA

Scott
fuente
2
Esto responde por qué y de ahí obtiene mi voto.
Little Ancient Forest Kami
1

Por lo general, ejecuta esto en bash. El proceso funciona y comienza simultáneamente, pero el shell los ejecuta en paralelo. ¿Como es posible?

  1. si no es el último comando en la tubería, cree una tubería sin nombre con un par de enchufes
  2. tenedor
  3. en el niño, reasigne stdin / stdout a sockets si es necesario (para el primer proceso en stdin de tubería no se reasigna, lo mismo para el último proceso y su stdout)
  4. en el comando EXEC secundario especificado con argumentos que barren el código de shell original, pero dejan todos abiertos por los sockets. La ID del proceso hijo no se cambiará porque este es el mismo proceso hijo
  5. Al mismo tiempo que el niño pero paralelo bajo el shell principal, vaya al paso 1.

el sistema no garantiza qué tan rápido se ejecutará el exec y se inicia el comando especificado. Es independiente del shell, pero del sistema. Esto es porque:

ps auxww| grep ps | cat

una vez show grepy / o pscomando, y ahora ahora. Depende de qué tan rápido el núcleo realmente inicie procesos usando la función de ejecución del sistema.

Znik
fuente
1
La ejecución concurrente significa que dos o más procesos se ejecutan dentro del mismo período de tiempo, generalmente con algún tipo de dependencia entre ellos. La ejecución paralela significa que dos o más procesos se ejecutan simultáneamente (por ejemplo, en núcleos de CPU separados al mismo tiempo). El paralelismo no es relevante para la pregunta, ni "cuán rápido" exec()se ejecuta, sino cómo exec()se intercalan las llamadas y la ejecución de los programas en una tubería .
Thomas Nyman