Se eu executar este comando bash e prefixar a instrução, para que a variável fruit
exista, mas apenas durante este comando:
$ fruit=apple echo $fruit
$
O resultado é uma linha vazia. por que?
Para citar um comentário do curinga emessa questão:
a expansão dos parâmetros é feita pelo shell, e a variável "fruit" não é uma variável do shell; é apenas uma variável de ambiente dentro do ambiente do comando "echo"
Uma variável de ambiente ainda é uma variável, então certamente ainda deve estar disponível para o comando echo?
Responder1
O problema é que o shell atual está expandindo a variável muito cedo; não está definido em seu contexto então o echo
comando não recebe nenhum argumento, ou seja, os comandos acabam sendo:
$ fruit=apple echo
Aqui está uma solução alternativa em que a variável não é expandida muito cedo por causa das aspas simples:
$ fruit=apple sh -c 'echo $fruit'
Alternativamente, você também pode usar um script shell de uma linha que demonstra que a fruit
variável foi passada corretamente para o comando executado:
$ cat /tmp/echof
echo $fruit
$ /tmp/echof
$ fruit=apple /tmp/echof
apple
$ echo $fruit
$
Alguns comentários sobre esta questão geraram algumas controvérsias e discussões inesperadas:
- O fato da variável
fruit
já estar exportada ou não não afeta o comportamento, o que importa é qual é o valor da variável no exato momento em que o shell a está expandindo.
$ exportar fruta=banana $ fruta = maçã echo $ fruta banana
- O fato de o
echo
comando ser integrado não afeta o problema do OP. No entanto, há casos em que o uso de funções internas ou shell com esta sintaxe causa efeitos colaterais inesperados, por exemplo:
$ exportar fruta=banana $ fruta = maçã eval 'echo $ fruta' maçã $ echo $ fruta maçã
Embora haja uma semelhança entrea pergunta feita aquie aquele, este não é exatamente o mesmo problema. Com essa outra pergunta, o
IFS
valor da variável temporária ainda não está disponível quando o shelldivisão de palavras outrovariável$var
enquanto estiver aqui, o valor da variável temporáriafruit
ainda não está disponível quando o shellexpandeomesmovariável.Há tambémaquela outra perguntaonde o OP pergunta sobre o significado da sintaxe usada e, mais precisamente, pergunta "por que isso funciona?".
Aqui o OP está ciente da importância, mas relata um comportamento inesperado e pergunta sobre sua causa, ou seja, "por que isso não funciona?".Ok, depois de ler mais de perto a pobre captura de tela postada na outra pergunta, a mesma situação está descrita lá (BAZ=jake echo $BAZ
) então sim, afinal isso é uma duplicata
Responder2
Para entender isso corretamente, vamos primeiro diferenciarvariáveis de shelldevariáveis ambientais.
Variáveis de ambiente são uma propriedade que TODOS os processos possuem, quer os utilizem internamente ou não. Mesmo sleep 10
quando está em execução possui variáveis de ambiente. Assim como todos os processos têm um PID (identificador de processo), um diretório de trabalho atual (cwd), um PPID (PID pai), uma lista de argumentos (mesmo que vazia) — e assim por diante. Da mesma forma, todos os processos têm o que é chamado de “ambiente”, que é herdado do processo pai quando ele se bifurca.
Do ponto de vista do autor do utilitário (alguém que escreve código em C), os processos têm a capacidade de definir, desativar ou alterar variáveis de ambiente. No entanto, do ponto de vista do autor do script, a maioria das ferramentas não oferece essa facilidade aos seus usuários. Em vez disso, você usa seuconchapara alterar o ambiente do processo que é herdado quando o comando (binário externo) que você chamou é executado. (O ambiente do seu próprio shell pode ser modificado e a modificação herdada, ou você pode direcionar seu shell para fazer a modificação após a bifurcação, mas antes de executar o comando que você chamou. De qualquer forma, o ambiente é herdado. Veremos ambos abordagens.)
Variáveis de shell são outra coisa. Emboradentro da cascaeles se comportam da mesma maneira, a diferença é que meras "variáveis de shell" não alteram ou influenciam o comportamento dos comandos que você chamadesua concha. Na terminologia adequada, a distinção seria, na verdade, formulada de forma um pouco diferente;exportadovariáveis de shell se tornarão parte do ambiente de ferramentas que você chama, enquanto variáveis de shell que sãonãoexportado não. No entanto, acho mais útil para a comunicação referir-se a variáveis de shell que não são exportadas como "variáveis de shell" e variáveis de shell quesãoexportados como "variáveis de ambiente" porquesãovariáveis de ambiente da perspectiva de processos bifurcados do shell.
Isso é muito texto. Vejamos alguns exemplos e descrevemos o que está acontecendo:
$ somevar=myfile
$ ls -l "$somevar"
-rw-r--r-- 1 Myname staff 0 May 29 19:12 myfile
$
Neste exemplo, somevar
é apenas uma variável shell, nada de especial nisso. Conchaexpansão de parâmetros(ver LESS='+/Parameter Expansion' man bash
) ocorreanteso ls
executável é realmente carregado ("exec"ed) e o ls
comando (processo) nuncavêa string "cifrão somevar". Ele vê apenas a string "myfile", interpreta-a como o caminho para um arquivo no diretório de trabalho atual e obtém e imprime informações sobre ele.
Se executarmos export somevar
antes do ls
comando, então o fato que somevar=myfile
aparecerá noambientedo ls
processo, mas isso não afetará nada porque o ls
comando não fará nada com esta variável. Para ver umefeitode uma variável de ambiente, temos que escolher uma variável de ambiente que o processo que estamos chamando irá realmente verificar e fazer algo com ela.
bc: Calculadora Básica
Pode haver um exemplo melhor, mas este foi um que eu criei e que não é muito complicado. Primeiro você deve saber que bc
é uma calculadora básica, que processa e calcula expressões matemáticas. (Depois de processar o conteúdo de qualquer arquivo de entrada, ele processa sua entrada padrão. Não vou usar sua entrada padrão para meus exemplos; apenas pressionarei Ctrl-D, que não será exibido nos trechos de texto abaixo. Além disso Estou usando -q
para suprimir uma mensagem introdutória em cada invocação.)
A variável de ambiente que ilustrarei está descrita em man bc
:
BC_ENV_ARGS
This is another mechanism to get arguments to bc. The format is
the same as the command line arguments. These arguments are
processed first, so any files listed in the environment argu-
ments are processed before any command line argument files.
This allows the user to set up "standard" options and files to
be processed at every invocation of bc. The files in the envi-
ronment variables would typically contain function definitions
for functions the user wants defined every time bc is run.
Aqui vai:
$ cat file1
5*5
$ bc -q file1
25
$ cat file2
6*7
8+9+10
$ bc -q file2
42
27
$ bc -q file1 file2
25
42
27
$
Isso é apenas para mostrar como bc
funciona. Em cada um desses casos, tive que pressionar Ctrl-D para sinalizar "fim da entrada" para bc
.
Agora, vamos passar uma variável de ambientediretamentepara bc
:
$ BC_ENV_ARGS=file1 bc -q file2
25
42
27
$ echo "$BC_ENV_ARGS"
$ bc -q file2
42
27
$
Observe que o que colocamos nessa variável énãovisível posteriormente a partir de um echo
comando. Ao colocar a atribuição como parte do mesmo comando (sem ponto e vírgula), colocamos essa atribuição de variável comopapeldo ambiente de bc
—ele deixou o próprio shell que estamos executando inalterado.
Agora vamos definir BC_ENV_ARGS
comoconchavariável:
$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$
Aqui você pode ver que nosso echo
comando pode ver o conteúdo, mas não faz parte do ambiente, bc
portanto bc
não podemos fazer nada de especial com ele.
É claro que se colocarmos a própria variável na bc
lista de argumentos de , veremos algo:
$ bc -q "$BC_ENV_ARGS"
25
$
Mas aqui, é oconchaexpandindo a variável, e então file1
é o que realmente aparece em bc
'slista de argumentos. Portanto, isso ainda é usado como uma variável de shell, não como uma variável de ambiente.
Agora vamos "exportar" esta variável para que seja uma variável de shell E uma variável de ambiente:
$ export BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$
E aqui você pode ver que file1
é processado antes file2
, embora não seja mencionado na linha de comando aqui. Faz parte do ambiente do shell e se torna parte do bc
ambiente quando você executa esse processo, então o valor desta variável de ambiente éherdadoe afeta como bc
funciona.
Ainda podemos substituir isso por comando, até mesmo substituí-lo por um valor vazio:
$ BC_ENV_ARGS= bc -q file2
42
27
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$
Mas como você pode ver, a variável permanece definida e exportada em nosso shell, visível tanto para o próprio shell quanto para quaisquer bc
comandos posteriores quenãosubstituir o valor. Ele permanecerá assim, a menos que o "desexportemos" ou "desmarquemos". Eu farei o último:
$ unset BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
$ bc -q file2
42
27
$
Outro exemplo, envolvendo a geração de outro shell:
Digite os seguintes comandos um após o outro em seu shell e considere os resultados. Veja se você consegue prever os resultados antes de executá-los.
# fruit is not set
echo "$fruit"
sh -c 'echo "$fruit"'
# fruit is set as a shell variable in the current shell only
fruit=apple
echo "$fruit"
sh -c 'echo "$fruit"'
sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only
# fruit is exported, so it's accessible in current AND new processes
export fruit
echo "$fruit"
sh -c 'echo "$fruit"'
echo '$fruit' ### I threw this in to make sure you're not confused on quoting
# fruit is unset again
unset fruit
echo "$fruit"
sh -c 'echo "$fruit"'
# setting fruit directly in environment of single command but NOT in current shell
fruit=apple sh -c 'echo "$fruit"'
echo "$fruit"
fruit=apple echo "$fruit"
# showing current shell is unaffected by directly setting env of single command
fruit=cherry
echo "$fruit"
fruit=apricot sh -c 'echo "$fruit"'
echo "$fruit"
sh -c 'echo "$fruit"'
E uma última para maior complexidade: você pode prever a saída dos seguintes comandos executados em sequência? :)
fruit=banana
fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach'
echo "$fruit"
Mencione nos comentários quaisquer esclarecimentos desejados; Tenho certeza que isso poderia usar alguns. Mas esperamos que seja útil, mesmo do jeito que está.
Responder3
Porque as expansões na linha de comando ocorrem antes das atribuições de variáveis.A norma diz isso:
Quando um determinado comando simples for necessário para ser executado, o seguinte ... deve ser executado:
As palavras reconhecidas como atribuições de variáveis [...] são salvas para processamento nas etapas 3 e 4.
As palavras que não sejam atribuições de variáveis ou redirecionamentos deverão ser ampliadas. [...]
Os redirecionamentos serão realizados conforme descrito em Redirecionamento.
Cada atribuição de variável deverá ser expandida [...] antes da atribuição do valor.
Observe a ordem: na primeira etapa, as tarefas são apenassalvou, então as outras palavras são expandidas e somente no final ocorrem as atribuições de variáveis.
Claro, é altamente provável que o padrão diga isso apenas porque sempre foi assim e eles apenas codificaram o comportamento existente. Não diz nada sobre a história ou o raciocínio por trás disso. O shell tem que identificar palavras que se parecem com atribuições em algum momento (apenas para não tomá-las como parte do comando), então suponho que poderia funcionar na outra ordem, atribuir variáveis primeiro e depois expandir qualquer coisa na linha de comando . (ou apenas faça da esquerda para a direita...)