Creación de subshell Bash con llaves

31

De acuerdo con esto , colocar una lista de comandos entre llaves hace que la lista se ejecute en el contexto de shell actual. No se crea ninguna subshell .

Usando pspara ver esto en acción

Esta es la jerarquía de proceso para una tubería de proceso ejecutada directamente en la línea de comando. 4398 es el PID para el shell de inicio de sesión:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

Ahora sigue la jerarquía de procesos para una tubería de proceso entre llaves entregadas directamente en la línea de comando. 4398 es el PID para el shell de inicio de sesión. Es similar a la jerarquía anterior que demuestra que todo se ejecuta en el contexto actual del shell :

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

Ahora, esta es la jerarquía del proceso cuando el sleepen la tubería se coloca dentro de llaves (por lo tanto, dos niveles de llaves en total)

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

¿Por qué bashtiene que crear un subshell para ejecutarse sleepen el tercer caso cuando la documentación establece que los comandos entre llaves se ejecutan en el contexto actual del shell?

iruvar
fuente
Interesante, supongo que es porque en el tercer caso, el grupo interno es parte de la canalización, y por lo tanto se ejecuta en sub-shell, por ejemplo, como lo sería cualquier otra llamada de función que sea parte de la canalización. ¿Tiene sentido?
Miroslav Koškár
2
No diría "El shell debe" simplemente porque sí ... Las tuberías no se ejecutan en el contexto del shell. Si la canalización consiste en nada más que comandos externos, entonces es suficiente crear subprocesos. { sleep 2 | command ps -H; }
Hauke ​​Laging

Respuestas:

26

En una tubería, todos los comandos se ejecutan simultáneamente (con sus stdout / stdin conectados por tuberías) en diferentes procesos.

En

cmd1 | cmd2 | cmd3

Los tres comandos se ejecutan en diferentes procesos, por lo que al menos dos de ellos deben ejecutarse en un proceso secundario. Algunos shells ejecutan uno de ellos en el proceso de shell actual (si está integrado reado si la canalización es el último comando del script), pero los bashejecuta en su propio proceso por separado (excepto con la lastpipeopción en bashversiones recientes y bajo algunas condiciones específicas )

{...}agrupa comandos. Si ese grupo es parte de una tubería, tiene que ejecutarse en un proceso separado al igual que un comando simple.

En:

{ a; b "$?"; } | c

Necesitamos un shell para evaluar que a; b "$?"es un proceso separado, por lo que necesitamos un subshell. El shell podría optimizar al no bifurcar, bya que es el último comando que se ejecuta en ese grupo. Algunas conchas lo hacen, pero aparentemente no bash.

Stéphane Chazelas
fuente
" Los tres comandos se ejecutan en diferentes procesos, por lo que al menos dos de ellos tienen que ejecutarse en una subshell " . ¿Por qué es necesaria una subshell en este caso? ¿Puede el shell padre no generar procesos de árbol? O bien, cuando dice " tiene que ejecutarse en un subshell ", ¿quiere decir que el shell se bifurcará solo y luego se ejecutará para cada cmd1 cmd2 y cmd3? Si ejecuto esto bash -c "sleep 112345 | cat | cat ", solo veo una bash creada y luego 3 hijos sin ninguna otra subestimación intercalada.
Hakan Baba
" Necesitamos un shell para evaluar que a; b" $? "Es un proceso separado, por lo que necesitamos un subshell " . ¿También podría ampliar el razonamiento? ¿Por qué necesitamos un subsheel para entender eso? ¿Qué se necesita para entender eso? Supongo que se requiere el análisis, pero ¿qué más? . ¿Puede el shell padre no analizar a; b "$?"? ¿Existe realmente una necesidad fundamental de un subsheel, o tal vez es una decisión de diseño / implementación en bash?
Hakan Baba
@HakanBaba, lo he cambiado a "proceso secundario" para evitar posibles confusiones.
Stéphane Chazelas
1
@HakanBaba, el análisis se realiza en el padre (el proceso que lee el código, el que ejecutó el intérprete a menos que se le haya pasado el código eval), pero la evaluación (ejecute el primer comando, espere, ejecute el segundo) es hecho en el niño, el que tiene stdout conectado a la tubería.
Stéphane Chazelas
en { sleep 2 | ps -H; }el padre bash ve sleep 2que requiere un fork / exec. Pero en { { sleep 2; } | ps -H; }el padre bash ve { sleep 2; }en otras palabras, algún código bash. Parece que el padre puede manejar el fork / exec sleep 2pero genera un nuevo bash recursivamente para manejar el código bash encontrado. Ese es mi entendimiento, ¿tiene sentido?
Hakan Baba
19

Anidar las llaves parece indicar que está creando un nivel adicional de alcance que requiere que se invoque un nuevo subconcha. Puede ver este efecto con la segunda copia de Bash en su ps -Hsalida.

Solo los procesos estipulados en el primer nivel de llaves se ejecutan dentro del alcance del shell Bash original. Cualquier llave rizada anidada se ejecutará en su propio shell Bash con ámbito.

Ejemplo

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

Sacando la | ps -Hmezcla solo para que podamos ver las llaves rizadas anidadas, podemos ejecutar ps auxf | lessen otro shell.

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

¡Pero espera hay mas!

Sin embargo, si saca las tuberías y usa esta forma de comando, veremos lo que realmente esperaría:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

Ahora, en la ventana de observación resultante, recibimos una actualización cada 2 segundos de lo que está sucediendo:

Aquí está el primero sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

Aquí está el segundo sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

Aquí está el tercero sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

Observe que los tres durmientes, aunque se invocan en diferentes niveles de anidación de llaves, se mantienen dentro del PID 5676 de Bash. Así que creo que su problema es autoinfligido con el uso de | ps -H.

Conclusiones

El uso de | ps -H(es decir, la tubería) está causando un subconcha adicional, así que no use ese método cuando intente interrogar qué está sucediendo.

slm
fuente
entonces, "Solo los procesos estipulados en el primer nivel de llaves se ejecutan dentro del alcance del shell Bash original".
xealits
@xealits: ¿es un seguimiento P que me preguntas?
slm
@slm es solo el énfasis en el punto principal de la respuesta, como lo vi. Los comandos en el primer nivel de llaves se ejecutan en la capa actual, las llaves anidadas crean nuevas capas. Los paréntesis difieren en la creación de subshell de inmediato, en el primer nivel. Si me equivoqué, corrígeme. Pero, como otros señalan, la pregunta inicial también tiene tuberías. Así, la creación de procesos separados. Y las llaves tienen que crear un caparazón cuando se usan para un proceso separado. Entonces, probablemente, es la razón del comportamiento en cuestión.
xealits
ahora, después de volver a leer su publicación, veo que me equivoqué: la declaración de anidación solo se refiere al caso de las tuberías. Por lo tanto, las llaves no crean nuevas conchas, a menos que envuelva un proceso separado con ellas, entonces tienen que hacerlo.
xealits
@xealits, eso es correcto.
slm
7

Publicaré los resultados de mis pruebas, lo que me lleva a la conclusión de que bash crea un subconjunto para un comando de grupo si y solo si es parte de la canalización, es similar a como se llamaría alguna función que también se llamaría en sub-shell.

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1
Miroslav Koškár
fuente
Mi A está mostrando esto también.
slm