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

94

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

A execução (echo $x)na linha de comando resulta em 1

A execução echo $xde um script resulta em nada, conforme o esperado

Igorio
fonte

Respostas:

134

Um subshell começa como uma cópia quase idêntica do processo original do shell. Sob o capô, o shell chama a forkchamada 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. Até a $$variável especial mantém o mesmo valor em subcascas: é 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. O Bash define BASHPIDo PID do processo do shell, que é alterado nos subshells. Bash, zsh e mksh organizam para $RANDOMgerar valores diferentes no pai e no subshell. Mas, além de casos especiais incorporados 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 que 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 é executado em um 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 os subshells não herdam traps personalizados: os sinais ignorados ( ) permanecem ignorados no subshell, mas outros traps ( SIGNAL ) são redefinidos para a ação padrão 4 .trap '' SIGNALtrap CODE

Um subshell é, portanto, diferente de executar um script. Um script é um programa separado. Esse programa separado pode coincidentemente ser também um script que é executado pelo mesmo intérprete que o pai, mas essa coincidência não dá ao programa separado nenhuma 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 aos programas executados.

Portanto:

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 no

x=1
perl -le 'print $x'

ou

x=1
python -c 'print x'

1 Uma exceção é o ksh93shell em que o garfo é otimizado e a maioria de seus efeitos colaterais são emulados.
2 Semântica, são cópias. Do ponto de vista da implementação, há muito compartilhamento acontecendo.
3 Para o lado direito, isso depende da casca.
4 Se você testar isso, observe que coisas como$(trap) podem relatar as armadilhas do shell original. Observe também que muitas conchas têm bugs nos casos de canto que envolvem armadilhas. 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 a ERRinterceptação do subshell aninhado no caso "dois subshells", mas não a ERRinterceptação do subshell intermediário - a set -Eopção deve propagar oERRarmadilha para todas as subcascas, mas a subcamada intermediária é otimizada e, portanto, não existe para executar sua ERRarmadilha.

Gilles
fonte
2
@Kusalananda No. ( x=out; (x=in; echo $x))
Gilles
2
@ flow2k Essa é a ordem de expansão para coisas que acontecem no mesmo nível. Mas você também precisa considerar como a expansão é misturada à avaliação. Quando a expansão requer a avaliação de uma construção aninhada, a construção interna é avaliada primeiro. Assim, por exemplo, para avaliar echo $(x=2; echo $x), o fragmento $(x=2; echo $x)precisa ser expandido. Isso requer avaliar o comando x=2; echo $x. A expansão de $xacontece durante esta avaliação, após avaliar a peça x=2.
Gilles
2
@ flow2k Não há ordem entre a expansão de parâmetros e a substituição de comandos. Observe que esta frase usa ponto e vírgula para separar as etapas de expansão, mas a expansão de parâmetro e a substituição de comando estão na mesma cláusula delimitada por ponto e vírgula (sim, é sutil). A ordem é importante quando uma das partes tem um efeito colateral que afeta a outra parte, por exemplo (com xnão ajustada) echo $(echo foo >somefile)${x-$(cat somefile)}ou echo $(echo $x),${x=1}.
Gilles
1
@Gilles; Estou confuso. Se um subshell é diferente de executar um script, por que se diz que: A execução de um shell script inicia um novo processo, um subshell. ? Além disso, um ambiente de sub-shell deve ser criado como uma duplicata do ambiente do shell . Portanto, ./file será executado no ambiente subshell e, portanto, deve herdar os parâmetros do shell definidos pela atribuição de variável.
haccks
2
@haccks A definição no ABS é uma aproximação e não muito boa. Os exemplos são bons, mas as duas primeiras linhas dessa página são tão simplificadas que estão erradas. A execução de um script a partir de outro script inicia um novo processo que não é um subshell. No SUS, as definições são corretas (mas nem sempre muito fáceis de entender). ./filenão é executado em um subshell. Veja também unix.stackexchange.com/q/261638 e unix.stackexchange.com/a/157962
Gilles:
15

Obviamente, sim, como toda a documentação diz, 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 todas as alterações feitas no subshell também não são feitas no pai.

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

man ksh:

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

man bash:

(Lista)

A lista é executada em um ambiente subshell (consulte AMBIENTE DE EXECUÇÃO DE COMANDO abaixo). As atribuições 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 do shell que são definidos por atribuição [...] de variável.
Substituição de comandos, comandos agrupados entre parênteses e comandos assíncronos são invocados em um ambiente de subshell que é uma duplicata do ambiente de shell, [...]

Mikel
fonte
3
Isso deve ser contrastado com When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following., que contém o item: · shell variables and functions marked for export, along with variables exported for the command, passed in the environment(da mesma man bashseção), que explica por que um echo $xscript não imprime nada se xnão for exportado.
Johan E