Bash: executando variáveis concatenadas

1

Eu tenho um script bash do Linux, para o efeito de:

COMMAND_TO_EXECUTE="foo "$1" --option1 --option2 "$2
exec $COMMAND_TO_EXECUTE

Problema: o script falha

Depuração de depuração , se eu echo $COMMAND_TO_EXECUTE , em seguida, recortar / colar o eco em uma janela de terminal, funcionará perfeitamente.

So .. A string atrás de $COMMAND_TO_EXECUTE é válida em um terminal, mas não é válida em um script. Há algo que devo fazer para $COMMAND_TO_EXECUTE antes de tentar executá-lo?

Informações amplificadoras: os comandos a executar foram wget ou curl , tenho os mesmos problemas com ambos. (Eu citei corretamente strings e escapei de caracteres como &). Como mencionado antes .. o comando funciona bem se eu fizer eco, depois cortar / colar.

Estou confuso e sinto que estou faltando algo elementar porque o comando funciona recortado / colado , mas não no script.

ATUALIZAÇÃO: A resposta do Polyergic abaixo funcionou para mim. bash -c "$COMMAND_TO_EXECUTE" é executado corretamente.

    
por Paulb 12.12.2014 / 21:10

3 respostas

1

O idioma que usei é este:

cmd="some command in a string"
"$cmd"

Note que, ao contrário de usar exec , o script de chamada continuará.

Algumas modificações podem ser necessárias se o seu comando incluir caracteres especiais.

Aqui está outra variação que usei (não me lembro por que fiz assim):

cmd="some command in a string"
bash -c "$cmd"

Aqui está um script de exemplo completo, que eu uso para suprimir a saída esperada de cronjobs:

#!/bin/sh

# otrap
# Copyright © 2007-2014 Shad Sterling <[email protected]>
# Released under CC-BY-SA, http://creativecommons.org/licenses/by-sa/4.0/

if [ "" = "$2" ]; then
    echo traps the output of a command and echos the output only if the size doesn\'t match
    echo USAGE: $0 \<size\> [\"test\"] \<command\>
    echo size is the expected output size
    echo \"test\" means echo the output even if the size does match
    echo command is the command to silence unless it\'s output doesn\'t match 
    exit 2
fi;

test=false
size=$1; shift
echo=false
if [ "test" == "$1" ]; then
test=true; shift
echo=true
fi
cmd="$*"

TF=~/tmp/otrap.#$PPID.log
if [ "false" != "$echo" ]; then
    echo file: "$TF"
    echo running: "$cmd"
fi
starttime='date +%R'
$SHELL -c "$cmd" > $TF 2>&1
ret=$?
endtime='date +%R'
ST='\'dirname $0\'/filesize $TF 2>&1'
if [ "$size" != "$ST" ]; then
    echo=true;
fi;
if [ "false" != "$echo" ]; then
    echo " command:" "$cmd"
    echo "   start:" $starttime
    echo "  finish:" $endtime
    echo "returned:" $ret
    echo "    size:" $ST
    echo "expected:" $size
    echo --------------------------------------------
    cat $TF
fi
rm $TF
exit $ret
    
por 13.12.2014 / 01:30
3

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.

por 13.12.2014 / 19:51
0

Como você digitou COMMAND-TO-EXECUTE não é um nome de variável de shell válido, remova os traços. Isso pode dar algumas pistas:

$ echo $COMMAND-TO-EXECUTE
-TO-EXECUTE

$ COMMAND-TO-EXECUTE=Test
COMMAND-TO-EXECUTE=Test: command not found

$ COMMAND_TO_EXECUTE=Test

$ echo $COMMAND_TO_EXECUTE
Test

$ 

Então "foo "$1" --option1 --option2 "$2 parece um pouco estranho. Se você quiser que COMMAND_TO_EXECUTE contenha " caracteres, altere isso para "foo \"$1\" --option1 --option2 $2" - também observe que movi a cotação no final. Por isso, você "protege" $ 2 para não causar efeitos estranhos antes de deixá-lo em exec .

Como você não deu um comando de exemplo real para testar / depurar, não consigo pensar em mais ...

    
por 12.12.2014 / 23:39

Tags