Por que uma variável é visível em um subshell?

15

O Learning Bash Book menciona que uma subshell herdará apenas variáveis de ambiente e descritores de arquivos, etc., e que não herdará variáveis que não sejam exportadas:

$ var=15
$ (echo $var)
15
$ ./file # this file include the same command echo $var

$

Como eu sei, o shell criará dois subshells para () e para ./file , mas por que no caso () a subshell identifica a variável var , embora não seja exportada e no ./file caso não o identificasse?

# Strace for () 
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25617
# Strace for ./file
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25631

Eu tentei usar strace para descobrir como isso acontece e surpreendentemente descobri que o bash usará os mesmos argumentos para a chamada de sistema clone, então isso significa que o processo bifurcado em () e ./file deve tem o mesmo espaço de endereço do processo do pai, então porque no () case é a variável visível para o subshell e o mesmo não acontece para o ./file case, embora os mesmos argumentos sejam baseados na chamada de sistema clone?

    
por user3718463 28.09.2014 / 00:37

2 respostas

11

O Livro de Bases de Aprendizagem está errado. Subshells herdam todas as variáveis. Mesmo $$ (o PID do shell original) é mantido. A razão é que para um subshell, o shell apenas bifurca e não executa um novo shell (ao contrário, quando você digita ./file , um novo comando é executado, por exemplo, um novo shell; na saída strace, veja execve e similar). Então, basicamente, é apenas uma cópia (com algumas diferenças documentadas).

Nota: isso não é específico para o bash; isso é verdade para qualquer shell.

    
por 28.09.2014 / 00:49
13

Você ou o livro está confundindo um subshell com um subprocesso que é um shell.

Algumas construções de shell fazem com que o shell bifurque o processo filho. No Linux, fork é um caso especial da chamada de sistema clone mais geral, que você observou no strace log. O filho executa uma parte do script de shell. O processo filho é chamado de subshell . A construção mais direta é command1 & : command1 é executada em um subshell e os comandos subseqüentes são executados no shell pai. Outras construções que criam uma subshell incluem a substituição de comando $(command2) e pipes command3 | command4 ( command3 é executado em uma subshell, command4 é executado em uma subshell na maioria das shells, mas não em ksh ou zsh).

Um subshell é uma cópia do processo pai, portanto, ele possui não apenas as mesmas variáveis de ambiente, mas também as mesmas definições internas: variáveis (incluindo $$ , o ID do processo original do shell), funções, aliases, opções, etc. Antes de executar o código no subshell, o bash define a variável BASHPID como o ID do processo filho.

Quando você executa ./file , isso executa um comando externo. Primeiro, o shell bifurca um processo filho; então este processo filho executa (com a chamada do sistema execve ) o arquivo executável ./file . Um processo filho herda os atributos de processo de seus pais: ambiente, diretório atual, etc. Aspectos internos do aplicativo são perdidos na chamada execve : variáveis não exportadas, funções, etc. são noções básicas que o kernel não sabe sobre, e eles são perdidos quando o bash executa outro programa. Mesmo que esse outro programa seja um script bash, ele é executado por uma nova instância do bash que não sabe ou não se importa que o processo pai também seja uma instância do bash. Assim, uma variável do shell (variável não exportada) não sobrevive a execve .

    
por 28.09.2014 / 02:14