¿Los paréntesis realmente ponen el comando en una subshell?

94

Por lo que he leído, poner un comando entre paréntesis debería ejecutarse en una subshell, similar a ejecutar un script. Si esto es cierto, ¿cómo ve la variable x si x no se exporta?

x=1

La ejecución (echo $x)en la línea de comando da como resultado 1

La ejecución echo $xde un script no da como resultado, como se esperaba

Igorio
fuente

Respuestas:

134

Una subshell comienza como una copia casi idéntica del proceso de shell original. Debajo del capó, el shell llama a la forkllamada al sistema 1 , que crea un nuevo proceso cuyo código y memoria son copias 2 . Cuando se crea el subshell, hay muy pocas diferencias entre él y su padre. En particular, tienen las mismas variables. Incluso la $$variable especial mantiene el mismo valor en subcapas: es la ID de proceso del shell original. Del mismo modo, $PPIDes el PID del padre del shell original.

Algunas shells cambian algunas variables en la subshell. Bash se establece BASHPIDen el PID del proceso de shell, que cambia en subcapas. Bash, zsh y mksh arreglan para $RANDOMproducir diferentes valores en el padre y en el subshell. Pero aparte de los casos especiales incorporados como estos, todas las variables tienen el mismo valor en el subshell que en el shell original, el mismo estado de exportación, el mismo estado de solo lectura, etc. Todas las definiciones de funciones, alias, opciones de shell y otras configuraciones también se heredan.

Una subshell creada por (…)tiene los mismos descriptores de archivo que su creador. Algunos otros medios para crear subshells modifican algunos descriptores de archivos antes de ejecutar el código de usuario; por ejemplo, el lado izquierdo de una tubería se ejecuta en un subshell 3 con salida estándar conectada a la tubería. La subshell también comienza con el mismo directorio actual, la misma máscara de señal, etc. Una de las pocas excepciones es que las subshell no heredan trampas personalizadas: las señales ignoradas ( ) permanecen ignoradas en la subshell, pero se restablecen otras trampas ( SEÑAL ) a la acción predeterminada 4 .trap '' SIGNALtrap CODE

Por lo tanto, una subshell es diferente de ejecutar un script. Un script es un programa separado. Este programa separado podría ser también una secuencia de comandos ejecutada por el mismo intérprete que el padre, pero esta coincidencia no le da al programa separado ninguna visibilidad especial sobre los datos internos del padre. Las variables no exportadas son datos internos, por lo que cuando se ejecuta el intérprete para el script de shell hijo , no ve estas variables. Las variables exportadas, es decir, las variables de entorno, se transmiten a los programas ejecutados.

Así:

x=1
(echo $x)

se imprime 1porque el subshell es una réplica del shell que lo generó.

x=1
sh -c 'echo $x'

sucede que ejecuta un shell como un proceso hijo de un shell, pero el xde la segunda línea no tiene más conexión con el xde la segunda línea que en

x=1
perl -le 'print $x'

o

x=1
python -c 'print x'

1 Una excepción es el ksh93shell donde se optimiza la bifurcación y se emulan la mayoría de sus efectos secundarios.
2 Semánticamente, son copias. Desde una perspectiva de implementación, se está compartiendo mucho.
3 Para el lado derecho, depende de la carcasa.
4 Si prueba esto, tenga en cuenta que cosas como$(trap) pueden informar las trampas del shell original. Tenga en cuenta también que muchos proyectiles tienen errores en casos de esquina que involucran trampas. Por ejemplo, ninjalj señala que a partir de bash 4.3, bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'ejecuta la ERRtrampa desde el subshell anidado en el caso de "dos subshell", pero no la ERRtrampa desde el subshell intermedio: la set -Eopción debe propagarERRtrap a todas las subcapas pero la subshell intermedia está optimizada y no está ahí para ejecutar su ERRtrampa.

Gilles
fuente
2
@Kusalananda No. ( x=out; (x=in; echo $x))
Gilles
2
@ flow2k Este es el orden de expansión para las cosas que suceden al mismo nivel. Pero también debe considerar cómo se combina la expansión con la evaluación. Cuando la expansión requiere la evaluación de una construcción anidada, la construcción interna se evalúa primero. Entonces, por ejemplo, para evaluar echo $(x=2; echo $x), el fragmento $(x=2; echo $x)necesita ser expandido. Esto requiere evaluar el comando x=2; echo $x. La expansión de $xocurre durante esta evaluación, después de evaluar la parte x=2.
Gilles
2
@ flow2k No hay orden entre la expansión de parámetros y la sustitución de comandos. Tenga en cuenta que esta oración usa punto y coma para separar los pasos de expansión, pero la expansión de parámetros y la sustitución de comandos están en la misma cláusula delimitada por punto y coma (sí, es sutil). El orden es importante cuando una de las partes tiene un efecto secundario que afecta a la otra parte, por ejemplo (con xunset) echo $(echo foo >somefile)${x-$(cat somefile)}o echo $(echo $x),${x=1}.
Gilles
1
@Gilles; Estoy confundido. Si un subshell es diferente de ejecutar un script, entonces por qué se dice que: Al ejecutar un script de shell se inicia un nuevo proceso, un subshell. ? Además, se creará un entorno subshell como duplicado del entorno shell . Por lo tanto, ./file se ejecutará en un entorno de subshell y, por lo tanto, debe heredar los parámetros de shell que se establecen mediante la asignación de variables.
piratea el
2
@haccks La definición en el ABS es una aproximación, y no muy buena. Los ejemplos son buenos, pero las dos primeras líneas de esa página están tan simplificadas que están equivocadas. La ejecución de un script desde otro script inicia un nuevo proceso que no es una subshell. En el SUS, las definiciones son correctas (pero no siempre son muy fáciles de entender). ./fileno se ejecuta en una subshell. Consulte también unix.stackexchange.com/q/261638 y unix.stackexchange.com/a/157962
Gilles el
15

Obviamente, sí, como dice toda la documentación, un comando entre paréntesis se ejecuta en una subshell.

La subshell hereda una copia de todas las variables del padre. La diferencia es que cualquier cambio que realice en la subshell tampoco se realiza en el padre.

La página de manual de ksh lo hace un poco más claro que el bash:

man ksh:

Un comando entre paréntesis se ejecuta en un sub-shell sin eliminar las variables no exportadas.

man bash:

(lista)

La lista se ejecuta en un entorno de subshell (ver ENTORNO DE EJECUCIÓN DE MANDOS a continuación). Las asignaciones variables y los comandos incorporados que afectan el entorno del shell no permanecen vigentes una vez que se completa el comando.

ENTORNO DE EJECUCIÓN DE MANDO

El shell tiene un entorno de ejecución, que consiste en lo siguiente: [...] parámetros del shell que se establecen por asignación de variable [...].
La sustitución de comandos, los comandos agrupados con paréntesis y los comandos asincrónicos se invocan en un entorno de subshell que es un duplicado del entorno de shell, [...]

Mikel
fuente
3
Esto se debe contrastar When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following., que contiene el elemento: · shell variables and functions marked for export, along with variables exported for the command, passed in the environment(de la misma man bashsección) que explica por qué un echo $xscript no imprime nada si xno se exporta.
Johan E