Comportamento da substituição do comando bash com o comando da string na variável

1
$ echo $(echo x; echo y)
x y
$ a='echo x; echo y'
$ echo $($a)  # expect 'x y'
x; echo y
  1. Por que a substituição de comandos se comporta dessa maneira?
  2. Como executar a substituição de comando para a lista de comandos armazenados em uma variável sem usar eval e bash -c ?

echo $(eval $a) e echo $(bash -c "$a") na verdade fazem o que eu quero, mas ouvi dizer que o uso de eval geralmente é um jeito errado de resolver problemas, então eu quero saber como gerenciar sem esses comandos (usar bash -c é de fato a mesma coisa)

    
por belkka 25.08.2017 / 17:35

2 respostas

3

A divisão de palavras acontece bastante tarde na avaliação de um comando. O mais importante para você é que ocorre após expansão da variável e substituição de comando.

Isso significa que a segunda linha em

s="echo a; echo b"
echo $($s)

primeiro terá $s expandido para echo a; echo b e então terá esse comando executado sem dividindo-o em um comando composto que consiste em dois echo s.

(detalhes: $s é dividido em quatro palavras, echo , a; , echo e b . O $(...) de encapsulamento executa isso como um comando, echo com três argumentos, a; , echo e b .)

O que é dado ao outmost echo é, portanto, a string a; echo b (na verdade, três palavras como $(...) não está entre aspas), já que foi o que foi gerado pelo mais interno echo .

Compare isso com

s1="echo a"
s2="echo b"
echo $($s1;$s2)

que resulta no que você esperaria.

Sim, " eval é mau", na maioria das vezes, e sh -c é desajeitado e tão frágil. Mas se você tem um pouco de código shell em que você confia em uma string em um shell script, essas ferramentas são a única maneira (?) De executar esse código corretamente, já que isso geralmente requer avaliar explicitamente o texto na string como código de shell (com todas as fases da avaliação do início ao fim). Especialmente se for um comando composto.

Eu acho que é apenas devido à relação íntima do shell Unix com o texto que você tem a sorte de ter echo $($s) executar algo .

Pense nas etapas que você deve seguir para obter um programa em C para executar um código em C que seja fornecido como uma string ...

    
por 25.08.2017 / 17:53
2

Depois de algumas leituras vou tentar responder mais particularmente.

Por que a substituição de comando se comporta dessa maneira?

$ a='echo x; echo y'
$ echo $($a)  # expect 'x y'

Substituição de comandos

Vamos notar que a substituição da variável $a está sendo executada na subshell produzida durante a substituição de comandos, não no shell atual ( 1 , 2 , 3 ). Portanto, o subshell executa o comando $a , não echo x; echo y (o valor da variável a é herdado)

Então, agora só precisamos entender por que o shell se comporta dessa maneira :

$ a='echo x; echo y'
$ $a
x; echo y

Divisão de palavras

De acordo com o [Manual de Referência da Bash]:

… There are seven kinds of expansion … The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion …

- da seção 3.5 Expansões da Shell

… The shell scans the results of parameter expansion, command substitution, and arithmetic expansion that did not occur within double quotes for word splitting.

The shell treats each character of $IFS as a delimiter, and splits the results of the other expansions into words using these characters as field terminators …

- da seção 3.5.7 Divisão de palavras

(O valor padrão de IFS é <space><tab><newline> )

… The IFS variable is used to split only the results of expansion, not all words (see Word Splitting) …

- do Apêndice B Principais diferenças do Bourne Shell

metacharacter

A character that, when unquoted, separates words. A metacharacter is a space, tab, newline, or one of the following characters: |, &, ;, (, ), <, or >.

- da seção 2. Definições

The brief description of the shell’s operation when it reads and executes a command. Basically, the shell does the following:

  1. Reads its input.
  2. Breaks the input into words and operators, obeying the quoting rules. These tokens are separated by metacharacters.
  3. Parses the tokens into simple and compound commands.
  4. Performs the various shell expansions.
  5. Performs any necessary redirections.
  6. Executes the command

- da seção 3.1.1 Operação da Shell

Agora, podemos ver que há dois tipos diferentes de "divisão de palavras" - a divisão de palavras "inicial" (etapa 2) e a divisão de palavras que são expansões de tipo de shell (passo 4).

A divisão de palavras "inicial" (etapa 2) respeita os metacaracteres como ; , ( , ) . O resultado da divisão "inital" de palavras é analisado (etapa 3) e todos os seus metacaracteres e palavras-chave são reconhecidos .

Subsequentemente, o bash executa as várias expansões de shell (etapa 4) e, entre outras, a divisão de palavras (como um tipo de expansão). "Esta" divisão de palavras trata apenas caracteres de IFS como delimitadores. "Esta" divisão de palavras não respeita metacaracteres . Resultado de "this" palavra splitting não é analisado novamente como resultado de qualquer expansão. Assim, metacharecters e palavras-chave no resultado de expansões não são reconhecidos .

É por isso que ; no valor da variável a não é tratado como metacaractere. $a se torna 'echo' 'x;' 'echo' 'y' . Mesmo se o valor de a fosse 'echo x ; echo y' , a palavra ';' não seria tratada como metacaractere e o comando $a se tornaria 'echo' 'x' ';' 'echo' 'y' .

Resumo:

Metacaracteres e palavras-chave ( if , then , while etc.) não podem ser um resultado de expansões. Mas nomes de programas (assim como comandos embutidos, funções e aliases) podem. É uma fonte de confusão, porque permite colocar comandos simples em variáveis e não permite colocar os compostos.

$ 'echo' 'a' ';' 'echo' 'b'  # ';' is a literal
a ; echo b

$ cmd=echo
$ "$cmd" a ; $(printf echo) b  # ';' is a metacharacter
a
b

$ cmd='echo a'  # simple command is executed properly
$ $cmd
a

$ cmd='echo a;'  # but metacharacters are not treated
$ $cmd echo b
a; echo b

$ echo a $(echo ';') echo b  # ';' is a literal
a ; echo b

$ $(printf if) true; then echo a; fi  # error while parsing
bash: syntax error near unexpected token 'then'

$ $(printf if) true; $(printf then) echo a; $(printf fi)  # command not found
bash: if: command not found
bash: then: command not found
bash: fi: command not found

Como executar a substituição do comando para a lista de comandos armazenados em uma variável sem usar eval e bash -c (já que eval é maligno)?

Probalby, a resposta certa é "isso também é ruim", então não precisamos evitar eval:)

Variables hold data. Functions hold code. Don't put code inside variables!..

- do artigo Estou tentando colocar um comando em uma variável, mas os casos complexos sempre falham!

Links

Unix stackexchange:

Bash substituição do comando shell

Por que uma variável é visível em um subshell?

Os parênteses realmente colocam o comando em um subshell?

Outro:

Manual de referência do Bash

Estou tentando colocar um comando em uma variável, mas os casos complexos sempre falham!

conversação no sistema de rastreamento de erros debian

    
por 27.08.2017 / 20:57