Bash: especificando variables ​​de ambiente para echo na linha de comando?

Considere este trecho:

$ SOMEVAR=AAA $ echo zzz $SOMEVAR zzz zzz AAA zzz 

Aqui eu configurei $SOMEVAR para AAA na primeira linha – e quando faço eco na segunda linha, recebo o conteúdo da AAA como esperado.

Mas então, se eu tentar especificar a variável na mesma linha de comando que o echo :

 $ SOMEVAR=BBB echo zzz $SOMEVAR zzz zzz AAA zzz 

… não obtenho BBB como esperava – recebo o valor antigo ( AAA ).

É assim que as coisas deveriam ser? Se sim, então como você pode especificar variables ​​como LD_PRELOAD=/... program args ... e fazer com que funcione? o que estou perdendo?

O que você vê é o comportamento esperado. O problema é que o shell pai avalia $SOMEVAR na linha de comandos antes de $SOMEVAR o comando com o ambiente modificado. Você precisa fazer com que a avaliação de $SOMEVAR adiada até que o ambiente esteja definido.

Suas opções imediatas incluem:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz .
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz' .

Ambos usam aspas simples para impedir que o shell pai avalie o $SOMEVAR ; ela é avaliada somente depois de ser definida no ambiente (temporariamente, pela duração do comando único).

Outra opção é usar a notação sub-shell (como também sugerido por Marcus Kuhn em sua resposta ):

 (SOMEVAR=BBB; echo zzz $SOMEVAR zzz) 

A variável é definida apenas no sub-shell

O problema, revisitado

Francamente, o manual é confuso neste ponto. O manual do GNU Bash diz:

O ambiente para qualquer comando ou function simples [observe que isso exclui os resources internos] pode ser aumentado temporariamente prefixando-o com atribuições de parâmetros, conforme descrito em parameters do shell. Estas instruções de atribuição afetam apenas o ambiente visto por esse comando.

Se você realmente analisar a sentença, o que está dizendo é que o ambiente para o comando / function é modificado, mas não o ambiente para o processo pai. Então, isso vai funcionar:

 $ TESTVAR=bbb env | fgrep TESTVAR TESTVAR=bbb 

porque o ambiente para o comando env foi modificado antes de ser executado. No entanto, isso não funcionará:

 $ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc + TESTVAR=bbb + echo aaa ccc aaa ccc 

por causa de quando a expansão do parâmetro é realizada pelo shell.

Etapas do Intérprete

Outra parte do problema é que o Bash define essas etapas para seu interpretador:

  1. Lê sua input de um arquivo (consulte Scripts de shell), de uma cadeia de caracteres fornecida como um argumento para a opção de chamada -c (consulte Chamando o Bash) ou do terminal do usuário.
  2. Divide a input em palavras e operadores, obedecendo às regras de cotação descritas no Quoting. Esses tokens são separados por metacaracteres. A expansão de alias é executada por essa etapa (consulte Aliases).
  3. Analisa os tokens em comandos simples e compostos (consulte Comandos do shell).
  4. Executa as várias expansões de shell (consulte Expansões de shell), dividindo os tokens expandidos em listas de nomes de arquivos (consulte Expansão de nome de arquivo) e comandos e argumentos.
  5. Executa quaisquer redirecionamentos necessários (consulte Redirecionamentos) e remove os operadores de redirecionamento e seus operandos da lista de argumentos.
  6. Executa o comando (consulte Execução de comandos).
  7. Opcionalmente, aguarda o comando concluir e coleta seu status de saída (consulte Sair do Status).

O que está acontecendo aqui é que os integrantes não obtêm seu próprio ambiente de execução, portanto, eles nunca vêem o ambiente modificado. Além disso, comandos simples (por exemplo, / bin / echo) obtêm um ennvironment modificado (é por isso que o exemplo env funcionou), mas a expansão do shell está ocorrendo no ambiente atual na etapa # 4.

Em outras palavras, você não está passando ‘aaa $ TESTVAR ccc’ para / bin / echo; você está passando a string interpolada (conforme expandida no ambiente atual) para / bin / echo. Neste caso, como o ambiente atual não possui TESTVAR , você está simplesmente passando ‘aaa ccc’ para o comando.

Resumo

A documentação poderia ser muito mais clara. Ainda bem que há Stack Overflow!

Veja também

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment

Para conseguir o que você quer, use

 ( SOMEVAR=BBB; echo zzz $SOMEVAR zzz ) 

Razão:

  • Você deve separar a atribuição por ponto-e-vírgula ou nova linha a partir do próximo comando, caso contrário, ela não será executada antes que a expansão do parâmetro aconteça para o próximo comando (eco).

  • Você precisa fazer a atribuição dentro de um ambiente de subshell , para garantir que não persista além da linha atual.

Esta solução é mais curta, mais limpa e mais eficiente do que algumas das outras sugeridas, em particular, não cria um novo processo.

A razão é que isso define uma variável de ambiente para uma linha. Mas, o echo não faz a expansão, o bash faz. Portanto, sua variável é realmente expandida antes que o comando seja executado, mesmo que SOME_VAR seja BBB no contexto do comando echo.

Para ver o efeito, você pode fazer algo como:

 $ SOME_VAR=BBB bash -c 'echo $SOME_VAR' BBB 

Aqui, a variável não é expandida até que o processo filho seja executado, portanto, você verá o valor atualizado. se você verificar SOME_VARIABLE novamente no shell pai, ele ainda será AAA , conforme esperado.

 SOMEVAR=BBB; echo zzz $SOMEVAR zzz 

Use um ; para separar instruções que estão na mesma linha.

Aqui está uma alternativa:

 SOMEVAR=BBB && echo zzz $SOMEVAR zzz