Os parênteses realmente colocam o comando em um subshell?

Os parênteses realmente colocam o comando em um subshell?

Pelo que li, colocar um comando entre parênteses deve executá-lo em um subshell, semelhante à execução de um script. Se isso for verdade, como ele vê a variável x se x não for exportado?

x=1

Executar (echo $x)na linha de comando resulta em 1

Executar echo $xum script não resulta em nada, como esperado

Responder1

Um subshell começa como uma cópia quase idêntica do processo shell original. Sob o capô, o shell chama oforkchamada de sistema 1 , que cria um novo processo cujo código e memória são cópias 2 . Quando o subshell é criado, há poucas diferenças entre ele e seu pai. Em particular, eles têm as mesmas variáveis. Até a $$variável especial mantém o mesmo valor nos subshells: é o ID do processo do shell original. Da mesma forma $PPIDé o PID do pai do shell original.

Alguns shells alteram algumas variáveis ​​no subshell. Bash ≥4.0 define BASHPIDo PID do processo shell, que muda em subshells. Bash, zsh e mksh organizam para $RANDOMproduzir valores diferentes no pai e no subshell. Mas, além de casos especiais integrados como esses, todas as variáveis ​​​​têm o mesmo valor no subshell que no shell original, o mesmo status de exportação, o mesmo status somente leitura, etc. Todas as definições de função, definições de alias, opções de shell e outras configurações também são herdadas.

Um subshell criado por (…)possui os mesmos descritores de arquivo de seu criador. Alguns outros meios de criação de subshells modificam alguns descritores de arquivo antes de executar o código do usuário; por exemplo, o lado esquerdo de um tubo corre em um subinvólucro 3 com saída padrão conectada ao tubo. O subshell também começa com o mesmo diretório atual, a mesma máscara de sinal, etc. Uma das poucas exceções é que os subshells não herdam armadilhas personalizadas: sinais ignorados ( ) permanecem ignorados no subshell, mas outras armadilhas (trap '' SIGNALtrap CODESINAL) são redefinidos para a ação padrão 4 .

Um subshell é, portanto, diferente de executar um script. Um script é um programa separado. Este programa separado pode coincidentemente ser também um script executado pelo mesmo intérprete que o pai, mas esta coincidência não dá ao programa separado qualquer visibilidade especial nos dados internos do pai. Variáveis ​​não exportadas são dados internos, portanto, quando o interpretador do script shell filho éexecutado, ele não vê essas variáveis. Variáveis ​​exportadas, ou seja, variáveis ​​de ambiente, são transmitidas para programas executados.

Por isso:

x=1
(echo $x)

imprime 1porque o subshell é uma replicação do shell que o gerou.

x=1
sh -c 'echo $x'

acontece de executar um shell como um processo filho de um shell, mas o xna segunda linha não tem mais conexão com o xna segunda linha do que em

x=1
perl -le 'print $x'

ou

x=1
python -c 'print x'

1 A menos que o shell otimize a bifurcação, mas emule a bifurcação tanto quanto necessário para preservar o comportamento do código que está executando. O Ksh93 otimiza muito, outros shells geralmente não.
2 Semanticamente, são cópias. Do ponto de vista da implementação, há muito compartilhamento acontecendo.
3 Para o lado direito depende da casca.
4 Se você testar isso, observe quecoisas como$(trap)pode relatar as armadilhas do shell original. Observe também que muitos shells apresentam bugs em casos extremos envolvendo armadilhas. Por exemploninjaljobserva que a partir do 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) )'executa o ERRtrap do subshell aninhado no caso de “dois subshells”, mas não o ERRtrap do subshell intermediário - set -Ea opção deve propagar o ERRtrap para todos os subshells, mas o subshell intermediário é otimizado e também é ' não está lá para executar sua ERRarmadilha.

Responder2

Obviamente, sim, como diz toda a documentação, um comando entre parênteses é executado em um subshell.

O subshell herda uma cópia de todas as variáveis ​​do pai. A diferença é que quaisquer alterações feitas no subshell também não são feitas no pai.

A página de manual do ksh deixa isso um pouco mais claro que a do bash:

man ksh:

Um comando entre parênteses é executado em um subshell sem remover variáveis ​​não exportadas.

man bash:

(lista)

list é executado em um ambiente subshell (consulte AMBIENTE DE EXECUÇÃO DE COMANDO abaixo). As atribuições de variáveis ​​e os comandos internos que afetam o ambiente do shell não permanecem em vigor após a conclusão do comando.

AMBIENTE DE EXECUÇÃO DE COMANDO

O shell possui um ambiente de execução, que consiste no seguinte: [...] parâmetros de shell que são definidos por atribuição de variáveis ​​[...].
Substituição de comandos comandos agrupados entre parênteses e comandos assíncronos são invocados em um ambiente subshell que é uma duplicata do ambiente shell

informação relacionada