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 ° consudo dtruss -t fork -f -p {pidOfShell1}
(el-f
es necesario también para rastrearfork()
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 llamadasfork()
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 llamadafork()
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.
- excepto directamente dentro de
- 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 shelllastpipe
(OFF por defecto), lo que hace que el último segmento de la tubería NO se ejecute en una subcadena.
- 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)).
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 ...)
).
- 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
- ejecución de fondo (
&
)
La combinación de estos constructos dará como resultado más de una subshell.