scripts script programas para manejo lineas linea leer fichero extraer ejemplos cadenas bash shell optimization subshell command-substitution

bash - programas - shell script leer fichero y extraer lineas



¿Cuándo la sustitución de comando genera más subcapas que los mismos comandos de forma aislada? (2)

En Bash, una subshell siempre se ejecuta en un nuevo espacio de proceso. Puede verificar esto de manera bastante trivial en Bash 4, que tiene las variables de entorno $BASHPID y $$ :

  • $$ Se expande al ID del proceso del shell. En una sub-shell (), se expande a la ID del proceso del shell actual, no de la subshell.
  • BASHPID Se expande a la identificación del proceso del proceso bash actual. Esto difiere de $$ en ciertas circunstancias, como subcapas que no requieren que bash se reinicialice

en la práctica:

$ type echo echo is a shell builtin $ echo $$-$BASHPID 4671-4671 $ ( echo $$-$BASHPID ) 4671-4929 $ echo $( echo $$-$BASHPID ) 4671-4930 $ echo $$-$BASHPID | { read; echo $REPLY:$$-$BASHPID; } 4671-5086:4671-5087 $ var=$(echo $$-$BASHPID ); echo $var 4671-5006

El único caso en que el shell puede eludir una subshell adicional es cuando canaliza a una subcapa explícita:

$ echo $$-$BASHPID | ( read; echo $REPLY:$$-$BASHPID; ) 4671-5118:4671-5119

Aquí, la subshell implícita en la tubería se aplica explícitamente, pero no se duplica.

Esto varía de algunos otros proyectiles que hacen un gran esfuerzo para evitar la fork . Por lo tanto, aunque siento que el argumento hecho en js-shell-parse engañoso, es verdad que no todas las conchas siempre se fork para todas las subcapas.

Ayer se me sugirió que el uso de la sustitución de comandos en bash provoca la creación de una subshell innecesaria. El consejo fue específico para este caso de uso :

# Extra subshell spawned foo=$(command; echo $?) # No extra subshell command foo=$?

Lo mejor que puedo imaginar es que esto parece ser correcto para este caso de uso. Sin embargo, una búsqueda rápida que intenta verificar esto lleva a montones de consejos confusos y contradictorios. Parece que la sabiduría popular dice que TODO uso de sustitución de comando generará una subcamada. Por ejemplo:

La sustitución de comando se expande a la salida de comandos. Estos comandos se ejecutan en una subshell , y sus datos stdout son a los que se expande la sintaxis de sustitución. ( source )

Esto parece bastante simple a menos que sigas cavando, en cuyo caso comenzarás a encontrar referencias a sugerencias de que este no es el caso.

La sustitución de comandos no necesariamente invoca una subshell , y en la mayoría de los casos no lo hará. Lo único que garantiza es la evaluación fuera de orden: simplemente evalúa las expresiones dentro de la sustitución primero, luego evalúa la afirmación circundante utilizando los resultados de la sustitución. ( source )

Esto parece razonable, pero ¿es cierto? Esta respuesta a una pregunta relacionada con la subshell me alertó de que el man bash tiene esto en cuenta:

Cada comando en una canalización se ejecuta como un proceso separado (es decir, en una subcadena).

Esto me lleva a la pregunta principal. ¿Qué hará exactamente que la sustitución de comando genere una subshell que de todos modos no se habría generado para ejecutar los mismos comandos de forma aislada?

Considere los siguientes casos y explique cuáles incurren en los gastos generales de una subshell extra:

# Case #1 command1 var=$(command1) # Case #2 command1 | command2 var=$(command1 | command2) # Case #3 command1 | command 2 ; var=$? var=$(command1 | command2 ; echo $?)

¿Cada uno de estos pares incurre en la misma cantidad de subcapas para ejecutar? ¿Hay alguna diferencia en las implementaciones POSIX vs. bash? ¿Existen otros casos en los que el uso de la sustitución de comandos genere una subcadena en la que no se ejecute el mismo conjunto de comandos de forma aislada?


Actualización y advertencia :

Esta respuesta tiene un pasado problemático en el que confiadamente reclamé cosas que resultaron no ser ciertas. Creo que tiene valor en su forma actual , pero ayúdenme a eliminar otras imprecisiones (o convencerme de que debería eliminarse del todo).

He revisado sustancialmente, y en gran parte destruido, esta respuesta después de que @kojiro señaló que mis métodos de prueba eran defectuosos (originalmente usé ps para buscar procesos secundarios, pero eso es demasiado lento para detectarlos siempre); un nuevo método de prueba se describe a continuación.

Originalmente dije que no todas las subcapas bash se ejecutan en su propio proceso hijo, pero eso no es cierto.

Como @kojiro declara en su respuesta, algunas shells (aparte de bash) DO a veces evitan la creación de procesos hijo para subsellas, así que, hablando en general en el mundo de las shells, no se debe asumir que una subshell implica un proceso hijo.

En cuanto a los casos de OP en bash (se supone que las instancias de command{n} son comandos simples ):

# Case #1 command1 # NO subshell var=$(command1) # 1 subshell (command substitution) # Case #2 command1 | command2 # 2 subshells (1 for each pipeline segment) var=$(command1 | command2) # 3 subshells: + 1 for command subst. # Case #3 command1 | command2 ; var=$? # 2 subshells (due to the pipeline) var=$(command1 | command2 ; echo $?) # 3 subshells: + 1 for command subst.; # note that the extra command doesn''t add # one

Parece que usar la sustitución de comandos ( $(...) ) siempre agrega una subshell extra en bash , al igual que el encerrar cualquier comando en (...) .

Creo, pero no estoy seguro de que estos resultados sean correctos ; así es como lo probé (bash 3.2.51 en OS X 10.9.1) - por favor dígame si este enfoque es defectuoso :

  • Se aseguró de que solo se ejecutaran 2 shell bash interactivos: uno para ejecutar los comandos y el otro para monitorear.
  • En el segundo shell, supervisé las llamadas fork() en el 1 ° con sudo dtruss -t fork -f -p {pidOfShell1} (el -f es necesario también para rastrear fork() llamadas "transitivamente", es decir, para incluir las creadas por subshells ellos mismos).
  • Usó solo el built-in : (no-op) en los comandos de prueba (para evitar confundir la imagen con llamadas fork() adicionales para ejecutables externos); específicamente:

    • :
    • $(:)
    • : | :
    • $(: | :)
    • : | :; :
    • $(: | :; :)
  • Solo se contaron las líneas de salida dtruss que contenían un PID distinto de cero (dado que cada proceso hijo también informa la llamada fork() que lo creó, pero con PID 0).

  • Resta 1 del número resultante, ya que ejecutar incluso solo un builtin desde un shell interactivo aparentemente implica al menos 1 fork() .
  • Finalmente, se supone que el recuento resultante representa el número de subcapas creadas.

A continuación se muestra lo que todavía creo que es correcto de mi publicación original: cuando bash crea subcapas.

bash crea subcapas en las siguientes situaciones:

  • para una expresión rodeada de paréntesis ( (...) )
    • excepto directamente dentro de [[ ... ]] , donde los paréntesis solo se usan para la agrupación lógica.
  • para cada segmento de una tubería ( | ), incluido el primero
    • Tenga en cuenta que cada subshell implicada es una clonación del shell original en términos de contenido (las subcélulas subshells en proceso se pueden bifurcar desde otras subcapas ( antes de que se ejecuten los comandos)).
      Por lo tanto, las modificaciones de las subcapas en segmentos de canal anteriores no afectan a las posteriores.
      (Por diseño, los comandos en una tubería se lanzan simultáneamente - la secuenciación solo ocurre a través de sus tuberías stdin / stdout conectadas).
    • bash 4.2+ tiene la opción shell lastpipe (OFF por defecto), lo que hace que el último segmento de la tubería NO se ejecute en una subcadena.
  • para la sustitución de comando ( $(...) )

  • para la sustitución de procesos ( <(...) )

    • típicamente crea 2 subshells; en el caso de un comando simple , a @konsolebox se le ocurrió una técnica para crear solo 1 : anteponer el comando simple con exec ( <(exec ...) ).
  • ejecución de fondo ( & )

La combinación de estos constructos dará como resultado más de una subshell.