Por que “A = 10 ecoa $ A” não imprime 10?

25

Este comando:

A=10 echo $A

imprime uma linha vazia. Por que não 10? Por que a configuração de ambiente temporário no local não funciona?

Quero saber o motivo e a explicação, e não a solução.

eu usei

LANG=C gcc ...

para forçar o gcc, use o idioma de fallback (inglês) em vez do idioma do sistema (chinês). Portanto, presumo que um VAR=valueprefixo configure um ambiente temporário para o comando a seguir. Mas parece que tenho algum mal-entendido.

Earth Engine
fonte

Respostas:

22

É uma questão de ordem em que ocorrem as diferentes etapas da avaliação de um comando.

A=10 echo $Aprimeiro analisa o comando em um comando simples composto por três palavras A=10, echoe $A. Então cada palavra passa por substituição de variável, ou seja, a transformação de expansões variáveis, como $Aem seus valores (estou omitindo etapas que não fazem nada visível).

Se Atem o valor fooinicialmente, o resultado dos passos de expansão é um comando simples que ainda tem três palavras: A=10, echoe foo. (O shell também se lembra nesse momento de quais caracteres estavam inicialmente entre aspas - nesse caso, nenhum.) O próximo passo é executar o comando. Como A=10começa com um nome de variável válido seguido por um sinal de igual, é tratado como uma atribuição; a variável Aé configurada 10no shell e no ambiente durante a execução do comando. (Normalmente, você precisa escrever export Apara ter Ano ambiente e não apenas como uma variável de shell; isso é uma exceção.) A próxima palavra não é uma atribuição, portanto é tratada como um nome de comando (é um comando interno). oechoO comando não depende de nenhuma variável, portanto, A=10 echo $Atem exatamente o mesmo efeito que echo $A.

Se você deseja definir uma variável apenas para a duração de um comando, mas levando em consideração a atribuição durante a execução do comando, é possível usar um subshell. Um subshell, indicado entre parênteses, torna todas as alterações de estado (atribuições de variáveis, diretório atual, definições de funções etc.) locais no subshell.

(A=10; echo $A)

Faça isso export A=10se desejar exportar a variável para o ambiente para que ela seja vista por programas externos.

Gilles 'SO- parar de ser mau'
fonte
Obrigado, posso dizer A=10 (echo $A)e receber 10?
Earth Engine
2
@EarthEngine Não, isso seria um erro de sintaxe. A atribuição deve estar no início de um comando simples (ou seja, apenas um nome de comando e alguns parâmetros e, opcionalmente, algumas atribuições iniciais e alguns redirecionamentos). A=10; (echo $A)saídas, 10mas também define Ao restante do script.
Gilles 'SO- stop be evil'
2
@EarthEngine Mas você pode dizer A=10 eval 'echo $A'. As aspas simples param de $Aser interpretadas até que toda a linha seja avaliada ... Nesse momento A = 10. Considero esta resposta mais correta do que a aceita.
Oli
Eu acho que essa é a explicação correta. A razão para o comportamento é exatamente a ordem de quando a expansão $Ae a atribuição de Aestão acontecendo. Por exemplo, A=5; A=6 let 'a=A'; echo $aretorna 6não 5e acho que não letinicia um subshell, pois é um comando interno.
David Ongaro
@EarthEngine: Quando se diz que a explicação correta é a ordem da avaliação, pode ser enganoso: nãoA=10 echo $A será definido para nenhum comando posteriormente, mesmo se eles estiverem em linhas diferentes (quando claramente a tarefa já foi avaliada). Não se trata de ordem, é de escopoA=10
MestreLion 30/05
37

Quando você usa LANG=C gcc ... o que acontece é que o shell define LANG para gcco ambiente da única , e não para o atual ambiente em si ( ver nota ). Portanto, após a gccconclusão, LANGvolta ao seu valor anterior (ou não definido).

Além disso, quando você o usa A=10 echo $A, é o shell que substitui $ A, não eco, e essa substituição (chamada "expansão") ocorre antes da avaliação da instrução (incluindo a atribuição), portanto, para funcionar como Ao valor esperado já deve estar definido no ambiente atual anterior a essa declaração.

É por isso A=10 echo $Aque não funciona conforme o esperado: A=10será definido para eco, mas o eco ignora internamente o valor da variável de ambiente A. E $Aé substituído pelo valor definido no shell atual (que não é nenhum) e depois passado como argumento para eco.

Portanto, o seu pressuposto é correto: VAR=value command faz o trabalho, mas isso só é relevante se commandinternamente usa VAR. Caso contrário, você ainda pode passar valuecomo argumento para command, mas os argumentos são substituídos pelo shell atual ; portanto, eles devem ser definidos antes do uso:VAR=value; command "$VAR"

Se você sabe como criar um script executável, tente isso como um teste:

#!/bin/sh
echo "1st argument is $1"
echo "A is $A"

Salve-o como testscripte tente:

$ A=5; A=10 testscript "$A"; echo "$A"
1st argument is 5
A is 10
5

Por último, mas não menos importante, vale a pena conhecer a diferença entre as variáveis shell e de ambiente e os argumentos do programa .

Aqui estão algumas boas referências:

.

(*) Nota: tecnicamente, o shell não definida no ambiente atual também, e aqui está o porquê: Alguns comandos, como echo, reade testsão builtins shell , e como tal eles não gerar um processo filho. Eles são executados no ambiente atual. Mas o shell cuida da tarefa apenas durando até que o comando seja executado; portanto, para todos os efeitos práticos, o efeito é o mesmo: a tarefa é vista apenas por esse único comando.

MestreLion
fonte
2
Essa explicação está realmente incorreta, embora resulte na conclusão correta em todos os casos, exceto em alguns casos extremos. A explicação real é a ordem da expansão: $Aé avaliada antes da atribuição. Eu acho que sua explicação falha apenas no caso de utilitários internos regulares cujo comportamento depende do valor da variável: o interno vê o valor atribuído. Um exemplo comum é o seguinte IFS=: read one two three rest: lê campos separados por dois pontos: o readbuilt-in vê o valor de IFS.
Gilles 'SO- stop be evil'
“Não para o próprio shell atual” está errado: a variável é definida no shell atual, mas dura apenas o comando simples atual. echoveria o valor 10para A, se ele se importava.
Gilles 'SO- stop be evil'
@ Gilles: muito obrigado pelo esclarecimento! Eu não estava ciente dessa sutileza. Portanto, se eu entendi corretamente, o bash precisa definir para o ambiente atual , caso contrário, os componentes internos (que não geram um novo pid) não verão a atribuição como outros comandos "regurlar". Mas ele é desabilitado após a execução do comando para limitar o escopo da atribuição. Isso está correto? Corrigirei minha resposta de acordo. PS: técnicos de lado, eu ainda acho que uma resposta deve enfatizar o aspecto escopo, não ordem de avaliação, caso contrário, pode-se pensar A=10 test; echo $Airá imprimir 10
MestreLion
3

Uma maneira possivelmente limpa de fazer o que você aparentemente deseja é emitir o comando:

A=10 eval 'echo $A'

O que, de fato, adiará a substituição do valor 10 para o lugar de $ A para um contexto posterior (ou seja, 'dentro' da avaliação, que já conhece a atribuição). Observe que as aspas simples são essenciais. Essa construção comunica claramente a atribuição ao comando desejado (eco neste caso) sem correr o risco de poluir o ambiente atual.

nhokka
fonte