Por que o bash não expande esta variável quando eu prefixo um comando com uma "atribuição única de variável"

Por que o bash não expande esta variável quando eu prefixo um comando com uma "atribuição única de variável"

Se eu executar este comando bash e prefixar a instrução, para que a variável fruitexista, 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 echocomando 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 fruitvariá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 fruitjá 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 echocomando 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 IFSvalor da variável temporária ainda não está disponível quando o shelldivisão de palavras outrovariável $varenquanto estiver aqui, o valor da variável temporária fruitainda 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 10quando 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 lsexecutável é realmente carregado ("exec"ed) e o lscomando (processo) nuncaa 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 somevarantes do lscomando, então o fato que somevar=myfileaparecerá noambientedo lsprocesso, mas isso não afetará nada porque o lscomando 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 -qpara 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 bcfunciona. 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 echocomando. 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_ARGScomoconchavariável:

$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$

Aqui você pode ver que nosso echocomando pode ver o conteúdo, mas não faz parte do ambiente, bcportanto bcnão podemos fazer nada de especial com ele.

É claro que se colocarmos a própria variável na bclista 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 bcambiente quando você executa esse processo, então o valor desta variável de ambiente éherdadoe afeta como bcfunciona.

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 bccomandos 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:

  1. As palavras reconhecidas como atribuições de variáveis ​​[...] são salvas para processamento nas etapas 3 e 4.

  2. As palavras que não sejam atribuições de variáveis ​​ou redirecionamentos deverão ser ampliadas. [...]

  3. Os redirecionamentos serão realizados conforme descrito em Redirecionamento.

  4. 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...)

informação relacionada