Na terminologia POSIX, um ambiente de subcasca está vinculado à noção de Shell Execution Environment .
Um ambiente de 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 do shell ...
As alterações nesse ambiente de subcasca não afetam o ambiente pai.
Tradicionalmente no shell Bourne ou no ksh88 no qual a especificação POSIX se baseia, isso era feito bifurcando um processo filho.
As áreas em que o POSIX requer ou permite a execução de comandos em um ambiente de subcamadas são aquelas em que o ksh88 tradicionalmente bifurcava um processo de shell filho.
No entanto, 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 desejar.
Por exemplo, o ksh93 faz isso salvando os atributos do ambiente de execução pai e restaurando-os após o término do ambiente do subshell em contextos onde a bifurcação pode ser evitada (uma otimização da bifurcação é bastante cara na maioria dos sistemas).
Por exemplo, em:
cd /foo; pwd
(cd /bar; pwd)
pwd
O POSIX exige cd /foo
que seja executado em um ambiente separado e que produza algo como:
/foo
/bar
/foo
Não requer que ele seja executado em um processo separado. Por exemplo, se stdout se tornar um pipe quebrado, a pwd
execução no ambiente de subshell pode muito bem ter o SIGPIPE enviado para o primeiro e único processo de shell.
A maioria dos shells, inclusive bash
, o implementará avaliando o código interno (...)
em um processo filho (enquanto o processo pai aguarda sua finalização), mas o ksh93 fará isso ao executar o código interno (...)
, tudo no mesmo processo:
- lembre-se de que está em um ambiente subshell.
- em
cd
seguida, 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 que cd
possa modificar e faça ochdir("/bar")
- ao retornar do subshell, o diretório de trabalho atual é restaurado (com um
fchdir()
no fd salvo) e tudo o mais que o subshell pode ter modificado.
Existem contextos em que um processo filho não pode ser evitado. O ksh93 não entra em:
var=$(subshell)
(subshell)
Mas faz em
{ subshell; } &
{ subshell; } | other command
Ou seja, os casos em que as coisas precisam ser executadas em processos separados para que possam ser executadas simultaneamente.
As otimizações do ksh93 vão além disso. Por exemplo, enquanto em
var=$(pwd)
a maioria dos shells faria bifurcar um processo, fazer o filho executar o pwd
comando com seu stdout redirecionado para um canal, pwd
gravar o diretório de trabalho atual nesse canal e o processo pai ler o resultado na outra extremidade do canal, ksh93
virtualizando tudo isso exigindo o garfo nem o tubo. Um garfo e tubo só seriam usados para comandos não integrados.
Observe que existem outros contextos que subshells para os quais shells bifurcam 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 esse comando retornar.
Dentro:
/bin/echo "$((n += 1))"
Isso não é um subshell, o comando será avaliado no atual ambiente de execução do shell, a n
variável do atual ambiente de execução do shell será incrementada, mas o shell 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 na medida em que não bifurcam um processo filho para executar esse comando externo, se for o último comando de um script ou um subshell (para os subshells implementados como processos filhos). ( bash
no entanto, somente o faz se esse comando for o único comando do subshell).
O que isso significa é que, com esses shells, se o último comando no subshell for um comando externo, o subshell não fará com que um processo extra seja gerado. 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, o segundo fork será feito anteriormente, para que a=2
seja executado em um ambiente de subshell.