Por que definir uma variável antes de um comando é legal no bash?

68

Acabei de encontrar várias respostas, como analisar um arquivo de texto delimitado ... que usam a construção:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

onde a IFSvariável é definida antes do readcomando.

Estive lendo a referência do bash, mas não consigo descobrir por que isso é legal.

eu tentei

$ x="once upon" y="a time" echo $x $y

no prompt de comando bash, mas não obteve eco. Alguém pode me indicar onde essa sintaxe é definida na referência que permite que a variável IFS seja definida dessa maneira? É um caso especial ou posso fazer algo semelhante com outras variáveis?

Mike Lippert
fonte
Consulte também Quando posso usar um IFS temporário para divisão de campos?
Gilles 'SO- stop be evil'

Respostas:

69

Está na Shell Grammar, Simple Commands (ênfase adicionada):

Um comando simples é uma sequência de atribuições de variáveis ​​opcionais, seguidas de palavras e redirecionamentos separados por branco , e finalizadas por um operador de controle. A primeira palavra especifica o comando a ser executado e é passado como argumento zero. As palavras restantes são passadas como argumentos para o comando invocado.

Então você pode passar qualquer variável que desejar. Seu echoexemplo não funciona porque as variáveis ​​são passadas para o comando, não configuradas no shell. O shell se expande $xe $y antes de chamar o comando. Isso funciona, por exemplo:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time
derobert
fonte
Obrigado! Eu pesquisei bastante antes de perguntar e estava tentando descobrir onde dizia isso na referência, sem sorte. Estou tentando melhorar a escrita de scripts bash e sua resposta ajuda.
precisa
11
Hmm, acho que preciso encontrar uma referência melhor, a minha (veja o link acima) não diz que não possui uma seção Gramática da Shell.
precisa
11
@MikeLippert Consulte a seção 3.7.4 nessa referência ("O ambiente para qualquer comando ou função simples pode ser aumentado temporariamente, prefixando-o com atribuições de parâmetros"). Eu acho que essa referência é de uma versão mais antiga do bash. Eu corri man bashno meu sistema ...
derobert
29

As variáveis ​​definidas se tornam como variáveis ​​de ambiente no processo bifurcado.

Se você correr

A="b" echo $A

em seguida, se expande bater em primeiro lugar $Ana ""e, em seguida, é executado

A="b" echo

Aqui está a maneira correta:

x="once upon" y="a time" bash -c 'echo $x $y'

Observe as aspas simples bash -c, caso contrário, você tem o mesmo problema acima.

Portanto, seu exemplo de loop é legal porque o comando bash builtin 'read' procurará o IFS em suas variáveis ​​de ambiente e o encontrará ,. Portanto,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

irá imprimir TEST is and I is test

Por fim, quanto à sintaxe, em um loop for é esperada uma string. Portanto, eu tive que usar backticks para transformá-lo em um comando. No entanto, enquanto loops esperam sintaxe de comando, como IFS=, read xx yy zz.

Mike Fairhurst
fonte
11
Obrigado, aprendi um pouco mais do que pedi na sua resposta. Também votaria na sua resposta, mas ainda não estou autorizado e sinalizei a resposta aceita no primeiro.
precisa
11
Obrigado, eu era o que eu estava esperando. E uma votação é menos significativa do que ouvir sua apreciação!
Mike Fairhurst
Para esclarecer seu comentário sob a primeira linha de código: Sim, com a Avariável não configurada bash se expande $Apara uma sequência vazia, mas para evitar confusão, eu não usaria ""porque o código não é equivalente a A="b" echo "". Não haverá argumento para echo.
pabouk
8

man bash

MEIO AMBIENTE

[...] O ambiente para qualquer comando ou função simples pode ser aumentado temporariamente, prefixando-o com atribuições de parâmetros, conforme descrito acima em PARÂMETROS. Essas instruções de atribuição afetam apenas o ambiente visto por esse comando.

As variáveis ​​são expandidas antes da atribuição da variável. Pela razão óbvia que var=xtambém funcionaria de outra maneira, mas var=$othervarnão funcionaria. Ou seja, o seu $xé necessário antes que esteja disponível. Mas esse não é o principal problema. O principal problema é que a linha de comando pode ser modificada apenas pelo ambiente do shell, mas a atribuição não se torna parte do ambiente do shell.

Você combina com os recursos: deseja uma substituição da linha de comandos, mas coloca a definição de variável no ambiente de comandos. As substituições da linha de comando devem ser feitas pelo shell. O ambiente deve ser usado explicitamente pelo comando chamado. Se e como isso é feito depende do comando.

A vantagem desse uso é que você pode configurar o ambiente para um subprocesso sem afetar o ambiente do shell.

x="once upon" y="a time" bash -c 'echo $x $y'

funciona como o esperado, pois nesse caso os dois recursos são combinados: A substituição da linha de comando não é feita pelo shell de chamada, mas pelo shell do subprocesso.

Hauke ​​Laging
fonte
11
É um pouco mais sutil que isso, porque o exemplo também funciona x="once upon" y="a time" eval 'echo $x $y'quando não há subprocesso envolvido, uma vez que evalestá embutido. Eu acho que a citação relevante da página de manual é The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. Considerando o exemplo da pergunta, ela deve ser assim, pois readtambém é um componente interno e funciona com um estado de alteração temporal de IFS.
21817 David Ongaro #:
4

O comando que você fornecer é diferente porque $xe $ysão expandidos antes de os echocomando é executado, para que seus valores no atual shell são usados, e não os valores que echoveria em seu ambiente se fosse para olhar.

chepner
fonte
Como algo na linha de comando pode ser expandido após a execução do comando ...?
Hauke Laging
As atribuições de pré-comando para xe ysão para o ambiente em que echoé executado, não o ambiente em que os argumentos echosão expandidos. Pois IFS=, read xx yy zz, a cadeia inteira é lida, sem divisão, pelo readcomando. Então , essa seqüência é dividida de acordo com o valor de IFS, com as peças correspondentes atribuídos a xx, yye zz.
chepner
Eu só queria salientar que a expressão "expandido antes da execução do comando" não faz muito sentido, porque após o início do comando, nada é mais expandido. Além disso: Você não deu uma única olhada na minha resposta ou ainda assim acredita que preciso de uma explicação do que está acontecendo ...?
Hauke Laging
11
Não afirmei que você precisava de uma explicação nem que qualquer coisa pudesse ser expandida após a execução do comando. No entanto, é verdade que bashprimeiro analisa a linha de comando especificada, reconhece que há duas atribuições de variáveis ​​a serem aplicadas ao ambiente do comando que vem, identifica o comando para executar ( echo), expande todos os parâmetros encontrados nos argumentos e executa o comando echocom os argumentos expandidos.
chepner
No caso de echoeu não ter certeza se poderia "ver" a variável, pois é um comando interno e, portanto, não é executado em um subshell que poderia ter seu próprio ambiente. Mas eu tentei com o evalqual também é um builtin e ele realmente sabe disso. Por exemplo, tente o a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $aque mostra que isso aé definido apenas evalmesmo que o pid seja o mesmo e o ambiente não seja alterado durante a avaliação! (Para acessar, /procvocê precisa executar isso no Linux.) Parece que o bash faz alguma mágica adicional aqui.
precisa
2

Eu estou indo para a imagem maior de " por que é legal"

Resposta: Para que você possa chamar ou chamar um programa e, para essa chamada, use apenas uma variável com uma variável específica.

Como exemplo: você tem um parâmetro para uma conexão com o banco de dados chamada 'db_connection' e normalmente passa 'test' como o nome da sua conexão com o banco de dados de teste. Na verdade, você pode até defini-lo como padrão que não precisa ser passado explicitamente. Às vezes, no entanto, você deseja trabalhar com o banco de dados ci. Então você passa o parâmetro como 'ci' e o programa chamado usa esse parâmetro do banco de dados como o nome do banco de dados a ser usado em todas as chamadas ao banco de dados. Para a próxima execução, se você não repetir a abordagem e apenas chamar o programa, a variável retornará ao seu valor padrão anterior.

Michael Durrant
fonte
0

Você também pode usar ;. Ele será avaliado anteriormente porque é um separador de comandos.

x="once upon" y="a time"; echo $x $y
camabeh
fonte
11
Isso cria duas variáveis ​​de shell e não cria variáveis ​​de ambiente no utilitário especificado. Isso é uma coisa muito diferente. Há também o efeito colateral de que o shell atual agora possui novas variáveis.
Kusalananda