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

78

Pelo que eu 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

Executando (echo $x) nos resultados da linha de comando em 1

A execução de echo $x em um script resulta em nada, como esperado

    
por Igorio 21.06.2014 / 19:50

2 respostas

115

Um subshell começa como uma cópia quase idêntica do processo shell original. Sob o capô, o shell chama a fork chamada de sistema 1 , que cria um novo processo cujo código e memória são cópias 2 . Quando o subshell é criado, existem muito poucas diferenças entre ele e seu pai. Em particular, eles têm as mesmas variáveis. Mesmo a variável $$ special mantém o mesmo valor em 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 define BASHPID para o PID do processo de shell, que muda em subshells. Bash, zsh e mksh organizam para que $RANDOM produza valores diferentes no pai e no subnível. Mas, além de casos especiais 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 criar subshells modificam alguns descritores de arquivos antes de executar o código do usuário; por exemplo, o lado esquerdo de um tubo é executado em uma subshell 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 subshells não herdam traps customizados: sinais ignorados ( trap '' SIGNAL ) permanecem ignorados no subshell, mas outros traps ( trap CODE SIGNAL) são redefinidos para a ação padrão 4 .

Um subshell é diferente da execução de um script. Um script é um programa separado. Este programa separado pode coincidentemente ser também um script que é 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. As variáveis não exportadas são dados internos, portanto, quando o intérprete para o script de shell filho é executado , ele não vê esses dados. variáveis. Variáveis exportadas, ou seja, variáveis de ambiente, são transmitidas para programas executados.

Assim:

x=1
(echo $x)

imprime 1 porque 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 x na segunda linha não tem mais conexão com o x na segunda linha do que em

x=1
perl -le 'print $x'

ou

x=1
python -c 'print x'

1 Uma exceção é o ksh93 shell onde o bifurcado é otimizado e a maioria de seus efeitos colaterais é emulada.
2 Semanticamente, são cópias. De uma perspectiva de implementação, há muito compartilhamento acontecendo.
3 Para o lado direito, depende do shell.
4 Se você testar isso, observe que coisas como $(trap) podem relatar as armadilhas do shell original. Note também que muitos shells possuem bugs em casos de canto envolvendo traps. Por exemplo ninjalj observa 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 ERR trap do subshell aninhado em o caso “two subshells”, mas não o ERR trap da subshell intermediária - set -E opção deve propagar o ERR trap para todas as subshells, mas o subshell intermediário é otimizado e não está lá para executar o ERR trap.

    
por 22.06.2014 / 01:52
14

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

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

A página man ksh torna isso um pouco mais claro que o bash:

man ksh :

A parenthesized command is executed in a sub-shell without removing non-exported variables.

man bash :

(list)

list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes.

COMMAND EXECUTION ENVIRONMENT

The shell has an execution environment, which consists of the following: [...] shell parameters that are set by variable assignment [...].
Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment, [...]

    
por 21.06.2014 / 20:03