Resposta curta: Veja BashFAQ # 50: Estou tentando colocar um comando em uma variável, mas os casos complexos sempre falham! .
Resposta longa: Quando o shell analisa uma linha de comando, ele faz coisas como descobrir quais partes da linha estão entre aspas (ou com escape ou o que quer que seja) antes substitui as variáveis; Assim, se você tiver quaisquer citações ou fugas dentro dos valores das variáveis, no momento em que elas forem substituídas no comando, é muito tarde para elas fazerem qualquer coisa. Ele analisa os valores das variáveis substitutas: divide-os em "palavras" com base em espaços, tabulações, etc. (não importa se estão entre aspas) e expande os curingas (novamente, mesmo que sejam estão nas cotações). BTW, também é tarde demais para que outras partes da sintaxe do shell, como pipes, redirecionamentos, etc. entrem em vigor. Para ilustrar isso, eu tenho um comando printargs
que imprime seus argumentos. Aqui está o que acontece quando tento armazenar um comando complexo em uma variável:
$ cmd='printargs " * " | cat >outfile &'
$ $cmd
Got 8 arguments:
'"'
'file1.txt'
'file2.txt'
'"'
'|'
'cat'
'>outfile'
'&'
Note que as aspas, pipe, etc são todos tratados como caracteres normais, não a sintaxe do shell, mas que o asterisco foi tratado como um curinga e substituído por uma lista de nomes de arquivos.
Existem várias soluções, dependendo do motivo pelo qual você colocou o comando em uma variável:
-
Se você realmente não precisa colocar o comando em uma variável, não . Os comandos são feitos para serem executados, portanto, a menos que haja uma boa razão para não fazê-lo, basta executá-lo diretamente.
-
Se você quiser usar essencialmente o mesmo comando várias vezes & não quero ter que escrever a coisa toda toda vez (a regra da programação "não se repita"), use uma função:
execute_command() {
foo "$1" --option1 --option2 "$2"
}
... e depois ligue repetidamente. Note que eu coloquei as referências de variáveis entre aspas duplas; você deve (quase) sempre fazer isso para evitar que a divisão de palavras e a expansão de curinga sejam aplicadas a eles.
-
Se você precisar criar o comando dinamicamente, use uma matriz:
post_data=("var1=value1" "var2=value2" ...)
post_args=()
for post_arg in "${post_data{@]}"; do # Note that this is the correct idiom for expanding an array in bash
post_data+=(-d "$post_arg")
done
curl "${post_args[@]}" "$url"
Observe que isso funciona para argumentos complexos para um único comando, mas não funciona para tarefas como pipes, redirecionamentos e planos de fundo ( &
), porque novamente eles são analisados antes que as variáveis sejam substituídas.
Finalmente, alguns avisos:
-
Não use eval
ou bash -c
a menos que você conheça exatamente como a análise de shell funciona (e se você está fazendo essa pergunta, não sabe exatamente como trabalhos de análise). Ambos causam uma camada extra de análise, o que tende a funcionar muito bem nos testes, mas falha ocasionalmente (por razões incompreensíveis). eval
tem uma reputação bem merecida como fonte de bugs realmente estranhos e sutis; e bash -c
faz essencialmente a mesma coisa, apenas com um subshell lançado para torná-lo ainda mais estranho.
-
Referências a variáveis com aspas duplas para impedir a divisão inesperada de palavras e a expansão de caracteres curinga.
-
Você provavelmente não quer usar exec
- ele sai do shell atual (e script shell) e o substitui pelo comando; isso provavelmente não é o que você pretendia.