Quais escopos podem ter variáveis ​​de shell?


42

Acabei de encontrar um problema que me mostra que não sou claro sobre o escopo das variáveis ​​do shell.

Eu estava tentando usar bundle install, que é um comando Ruby que usa o valor de $GEM_HOMEpara fazer seu trabalho. Eu tinha definido $GEM_HOME, mas o comando ignorou esse valor até que eu usei export, como em export GEM_HOME=/some/path.

Li que isso torna a variável de alguma forma "global" (também conhecida como variável de ambiente ), mas não entendo o que isso significa. Eu sei sobre globais em programação, mas não em programas distintos.

Além disso, como minha configuração dessas variáveis ​​se aplica apenas à sessão atual do shell, como eu as definiria para, por exemplo, um processo daemonizado?

Quais escopos podem ter variáveis ​​de shell?

Respostas:


33

Os processos são organizados como uma árvore: todo processo tem um pai único, além do initque PIDé sempre 1 e não tem pai.

A criação de um novo processo geralmente passa por um par de fork/ execvchamadas de sistema, em que o ambiente do processo filho é uma cópia do processo pai.

Para colocar uma variável no ambiente a partir do shell, você precisa exportdessa variável, para que seja visível recursivamente a todos os filhos. Mas lembre-se de que, se um filho alterar o valor de uma variável, o valor alterado será visível apenas para ele e todos os processos criados após essa alteração (sendo uma cópia , como dito anteriormente).

Leve também em consideração que um processo filho pode mudar seu ambiente, por exemplo, redefini-lo para os valores padrão, como provavelmente é feito loginpor exemplo.


1
Ah! OK, vamos ver se eu entendo isso. No shell, se eu disser FOO=bar, isso define o valor do processo atual do shell. Se eu executar um programa como ( bundle install), que cria um processo filho, ao qual não há acesso FOO. Mas se eu tivesse dito export FOO=bar, o processo filho (e seus descendentes) teria acesso a ele. Um deles poderia, por sua vez, pedir export FOO=buzzpara alterar o valor de seus descendentes, ou apenas FOO=buzzalterar o valor apenas para si. Isso é certo?
Nathan Long

2
@NathanLong Não é exatamente isso: em todos os shells modernos, uma variável é exportada (e, portanto, qualquer alteração no valor é refletida no ambiente dos descendentes) ou não é exportada (o que significa que a variável não está no ambiente). Em particular, se a variável já estiver no ambiente quando o shell for iniciado, ela será exportada.
Gilles 'SO- stop be evil'

2
Fiquei um pouco confuso com a frase "se uma criança alterar o valor de uma variável, o valor alterado será visível apenas para ela e todos os processos criados após essa alteração". Seria mais correto dizer "... visível para ele e todos os seus processos descendentes criados após essa alteração" - os outros filhos do processo pai, mesmo aqueles iniciados após o processo filho, não são afetados.
Jaan

26

Pelo menos as variáveis under kshe bash, podem ter três escopos, e não dois, como todas as respostas restantes estão dizendo no momento.

Além dos escopos variáveis ​​exportados (isto é, do ambiente) e dos escopos variáveis ​​não exportados, há também um terceiro mais restrito para variáveis ​​locais de função.

Variáveis ​​declaradas em funções de shell com o typesettoken são visíveis apenas dentro das funções em que são declaradas e nas (sub) funções chamadas a partir daí.

Este ksh/ bashcódigo:

# Create a shell script named /tmp/show that displays the scoped variables values.    
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show

# Function local variable declaration
function f
{
    typeset local=three
    echo "in function":
    . /tmp/show 
}

# Global variable declaration
export environment=one

# Unexported (i.e. local) variable declaration
shell=two

# Call the function that creates a function local variable and
# display all three variable values from inside the function
f

# Display the three values from outside the function
echo "in shell":
. /tmp/show 

# Display the same values from a subshell
echo "in subshell":
/tmp/show

# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show 

produz esta saída:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []

Como você pode ver, a variável exportada é exibida nos três primeiros locais, as variáveis ​​não exportadas não são exibidas fora do shell atual e a variável local da função não tem valor fora da própria função. O último teste não mostra nenhum valor, isso ocorre porque as variáveis ​​exportadas não são compartilhadas entre os shells, ou seja, elas só podem ser herdadas e o valor herdado não pode ser afetado posteriormente pelo shell pai.

Observe que esse último comportamento é bem diferente do comportamento do Windows, no qual você pode usar as Variáveis ​​do sistema totalmente globais e compartilhadas por todos os processos.


12

Eles têm escopo definido pelo processo

Os outros respondedores me ajudaram a entender que o escopo da variável shell é sobre processos e seus descendentes .

Quando você digita um comando como lsna linha de comando, você está realmente bifurcação um processo para executar o lsprograma. O novo processo tem seu shell como pai.

Qualquer processo pode ter suas próprias variáveis ​​"locais", que não são passadas para processos filho. Também pode definir variáveis ​​de "ambiente", quais são. Usar exportcria uma variável de ambiente. Em ambos os casos, processos não relacionados (pares do original) não verão a variável; estamos apenas controlando o que os processos filhos veem.

Suponha que você tenha um shell bash, que chamaremos de A. Você digita bash, que cria um shell bash do processo filho, que chamaremos de B. Qualquer coisa que você chamou exportem A ainda será definida em B.

Agora, em B, você diz FOO=b. Uma de duas coisas acontecerá:

  • Se B não recebeu (de A) uma variável de ambiente chamada FOO, ela criará uma variável local. Filhos de B não o receberão (a menos que B ligue export).
  • Se B se receber (de A) uma variável de ambiente callled FOO, ele irá modificá-lo para si e seus filhos posteriormente bifurcados . Filhos de B verão o valor que B atribuiu. No entanto, isso não afetará A.

Aqui está uma demonstração rápida.

FOO=a      # set "local" environment variable
echo $FOO  # 'a'
bash       # forks a child process for the new shell
echo $FOO  # not set
exit       # return to original shell
echo $FOO  # still 'a'

export FOO # make FOO an environment variable
bash       # fork a new "child" shell
echo $FOO  # outputs 'a'
FOO=b      # modifies environment (not local) variable
bash       # fork "grandchild" shell
echo $FOO  # outputs 'b'
exit       # back to child shell
exit       # back to original shell
echo $FOO  # outputs 'a'

Tudo isso explica meu problema original: coloquei GEM_HOMEno meu shell, mas quando liguei bundle install, isso criou um processo filho. Por não ter usado export, o processo filho não recebeu o shell GEM_HOME.

Não exportando

Você pode "cancelar a exportação" de uma variável - impedindo que ela seja passada para crianças - usando export -n FOO.

export FOO=a   # Set environment variable
bash           # fork a shell
echo $FOO      # outputs 'a'
export -n FOO  # remove environment var for children
bash           # fork a shell
echo $FOO      # Not set
exit           # back up a level
echo $FOO      # outputs 'a' - still a local variable

1
Quando você diz "ele será modificado para si e para seus filhos", você deve esclarecer que apenas os filhos criados após a modificação verão o valor modificado.
enzotib

1
@enzotib - bom ponto. Atualizada.
Nathan Long

3

A melhor explicação que posso encontrar sobre a exportação é esta:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

O conjunto de variáveis ​​dentro de um subshell ou shell filho é visível apenas para o subshell no qual está definido. A variável exportada é realmente feita para ser uma variável de ambiente. Portanto, para ficar claro, o seu bundle installexecuta seu próprio shell, que não vê o mesmo $GEM_HOME, a menos que seja transformada em uma environmentvariável conhecida como exportada.

Você pode dar uma olhada na documentação para escopo variável aqui:

http://www.tldp.org/LDP/abs/html/subshells.html


Ah, então eu estava incorreto ao usar o termo "variável de ambiente" para FOO=bar; você tem que usar exportpara torná-lo um. Pergunta corrigida em conformidade.
Nathan Long

Dê uma olhada no link que adicionei.
Karlson

3

Há uma hierarquia de escopos variáveis, conforme o esperado.

Meio Ambiente

O escopo mais externo é o meio ambiente. Esse é o único escopo gerenciado pelo sistema operacional e, portanto, é garantido que existe para todos os processos. Quando um processo é iniciado, ele recebe uma cópia do ambiente de seus pais, após o que os dois se tornam independentes: modificar o ambiente do filho não altera o ambiente dos pais e modificar o ambiente dos pais não altera o de um filho já existente.

Variáveis ​​do shell

Os shells têm sua própria noção de variáveis. É aqui que as coisas começam a ficar um pouco confusas.

Quando você atribui um valor a uma variável em um shell, e essa variável já existe no ambiente, a variável de ambiente recebe o novo valor. No entanto, se a variável não estiver no ambiente, ela se tornará uma variável do shell . As variáveis ​​do shell existem apenas dentro do processo do shell, semelhante à forma como as variáveis ​​Ruby existem apenas dentro de um script Ruby. Eles nunca são herdados por processos filhos.

Aqui é onde a exportpalavra - chave entra em ação. Ele copia uma variável de shell no ambiente do processo de shell, possibilitando a herança de processos filhos.

Variáveis ​​locais

Variáveis ​​locais são variáveis ​​de shell com escopo definido para os blocos de código que as contêm. Você declara variáveis ​​locais com a typesetpalavra - chave (portátil) ou localou declare(Bash). Como outras variáveis ​​de shell, as variáveis ​​locais não são herdadas pelos processos filhos. Também variáveis ​​locais não podem ser exportadas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.