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?


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

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.


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.


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.


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.


0

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

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.