Na terminologia POSIX, um ambiente subshell está vinculado à noção de Ambiente de Execução da Shell .
Um ambiente subshell é um ambiente de execução de shell separado criado como uma duplicata do ambiente pai. Esse ambiente de execução inclui coisas como arquivos abertos, umask, diretório de trabalho, variáveis / funções / aliases de shell ...
As alterações nesse ambiente de subshell não afetam o ambiente pai.
Tradicionalmente, no shell Bourne ou no ksh88, no qual a especificação POSIX é baseada, isso foi feito ao bifurcar um processo filho.
As áreas onde o POSIX requer ou permite que o comando seja executado em um ambiente de subshell são aquelas em que tradicionalmente o ksh88 bifurcava um processo de shell filho.
No entanto, isso não força as implementações a usar um processo filho para isso.
Um shell pode optar por implementar esse ambiente de execução separado da maneira que quiser.
Por exemplo, o ksh93 faz isso salvando os atributos do ambiente de execução pai e restaurando-os na finalização do ambiente subshell em contextos nos quais o forking pode ser evitado (como uma otimização, o forking é muito caro na maioria dos sistemas). p>
Por exemplo, em:
cd /foo; pwd
(cd /bar; pwd)
pwd
O POSIX requer que o cd /foo
seja executado em um ambiente separado e que produza algo como:
/foo
/bar
/foo
Não é necessário que ele seja executado em um processo separado. Por exemplo, se o stdout se tornar um cano quebrado, pwd
executado no ambiente subshell poderia muito bem ter o SIGPIPE enviado para o único processo de shell.
A maioria dos shells incluindo bash
irão implementá-lo avaliando o código dentro de (...)
em um processo filho (enquanto o processo pai aguarda sua finalização), mas o ksh93 irá executar o código dentro de (...)
, all in o mesmo processo:
- lembre-se que está em um ambiente subshell.
- após
cd
, salve o diretório de trabalho anterior (geralmente em um descritor de arquivo aberto com O_CLOEXEC), salve o valor das variáveis OLDPWD, PWD e qualquer coisa quecd
possa modificar e, em seguida, faça ochdir("/bar")
- Ao retornar do subshell, o diretório de trabalho atual é restaurado (com
fchdir()
no fd salvo) e tudo o mais que a subshell pode ter modificado.
Existem contextos em que um processo filho não pode ser evitado. O ksh93 não entra em garfo:
-
var=$(subshell)
-
(subshell)
Mas em
-
{ subshell; } &
-
{ subshell; } | other command
Ou seja, os casos em que as coisas precisam ser executadas em processos separados para que possam ser executados simultaneamente.
As otimizações do ksh93 vão além disso. Por exemplo, enquanto em
var=$(pwd)
a maioria das shells processaria um processo, faça o filho executar o comando pwd
com seu stdout redirecionado para um pipe, pwd
escreva o diretório de trabalho atual para esse canal e o processo pai leia o resultado na outra extremidade do tubo, ksh93
virtualiza tudo isso sem exigir o garfo nem o tubo. Um fork e pipe seriam usados apenas para comandos não incorporados.
Note que existem contextos que não são subchaves para os quais os shells forjam um processo filho. Por exemplo, para executar um comando que é armazenado em um executável separado (e que não é um script destinado ao mesmo interpretador de shell), um shell teria que bifurcar um processo para executar esse comando nele, caso contrário não seria capaz de executar mais comandos depois que o comando retornar.
Em:
/bin/echo "$((n += 1))"
Isso não é um subshell, o comando será avaliado no ambiente atual de execução do shell, a variável n
do ambiente atual de execução do shell será incrementada, mas o shell irá bifurcar um processo filho para executar esse /bin/echo
comando nele com a expansão de $((n += 1))
como argumento.
Muitos shells implementam uma otimização porque não executam um processo filho para executar esse comando externo se este for o último comando de um script ou de um subshell (para as subshells implementadas como processos-filhos).
( bash
, no entanto, só faz isso se esse comando for o único comando da subshell).
O que isso significa é que, com esses shells, se o último comando na subshell for um comando externo, o subshell não causará a geração de um processo extra. Se você comparar:
a=1; /bin/echo "$a"; a=2; /bin/echo "$a"
com
a=1; /bin/echo "$a"; (a=2; /bin/echo "$a")
haverá o mesmo número de processos criados, somente no segundo caso, a segunda bifurcação é feita antes para que o a=2
seja executado em um ambiente de subshell.