Executar um comando armazenado em uma variável

11

Eu tenho um comando armazenado em uma variável. Vamos fingir que a variável $item o valor:

cat -nT index.php |grep 'someregex'

Quando tento executar a variável acima, digitando $i-a falha porque o shell tenta executar a variável inteira como um comando. Eu também tentei usar eval($i)e colocar $ibackticks.

Como posso fazer o shell executar $icomo se fosse um comando? E por que não está funcionando da mesma maneira que.

$i='echo hi'; $i

É porque eu tive que meio que invadir as aspas simples? (Porque você não pode aninhá-los.) Atualmente, minha solução é

echo $i > /foo;  . /foo

Mas não quero criar um arquivo apenas para isso, apenas para excluí-lo mais tarde.

O que quero dizer com "hackeei as aspas simples é o que fiz:

$i='cat index.php | grep -P '"'"'MYREGEXHERE'"'"
Leathan
fonte
4
A questão é: por que o comando é armazenado em uma variável em primeiro lugar? É melhor montar o comando quando executá-lo, armazenando os argumentos das opções em variáveis ​​ou em uma matriz. Você pode mostrar o script inteiro que possui?
slhck
Mesma causa raiz: superuser.com/questions/360966/…
Ciro Santilli escreveu:

Respostas:

18

Resposta curta: consulte BashAQ # 50: Estou tentando colocar um comando em uma variável, mas os casos complexos sempre falham! .

Resposta longa: é por causa da ordem em que o bash analisa a linha de comando. Especificamente, ele procura coisas como pipes e redirecionamentos antes de expandir valores variáveis, e não volta e procura novamente pipes etc. nos valores expandidos. Essencialmente, as variáveis ​​são substituídas na metade do processo de análise, portanto, o valor é analisado apenas na metade antes de ser executado.

Para resolver isso, você precisa responder à pergunta de @ slhck: por que o comando é armazenado em uma variável em primeiro lugar? Qual é o problema real que você está tentando resolver? Dependendo do objetivo real, existem várias soluções possíveis:

  • Não o armazene em uma variável, apenas execute-o diretamente. O armazenamento de comandos para uso posterior é complicado e, se você realmente não precisa, simplesmente não precisa.

  • Use uma função em vez de uma variável. Afinal, é para isso que eles servem:

    i() { cat -nT index.php |grep 'someregex'; }
    i

    A principal desvantagem disso é que você não pode criar a função dinamicamente - você não pode incluir ou excluir condicionalmente o código da função (embora a própria função possa ter elementos condicionais que são selecionados quando são executados).

  • Use eval. Isso deve ser considerado um último recurso, pois é fácil obter um comportamento inesperado. Essencialmente, ele executa o comando através de outra passagem de análise completa, para que todos os pipes etc. tenham todo o significado - mas também significa que partes do comando que você pensou serem apenas dados também serão analisadas e talvez executadas. Os nomes de arquivos que contêm metacaracteres de shell (tubo, ponto-e-vírgula, aspas / apóstrofo, etc.) podem ter efeitos estranhos e às vezes perigosos. Se você usar eval, pelo menos aspas duplas na string, caso contrário, seu conteúdo será essencialmente analisado uma vez e meia, com resultados ainda mais estranhos.

    i="cat -nT index.php |grep 'someregex'"
    eval "$i"

Edição: Outra abordagem padrão de armazenar um comando em uma variável é usar uma matriz em vez de uma variável simples. Isso permite que você armazene um comando com argumentos complexos (por exemplo, contendo espaços) sem problemas, mas não armazena itens como pipes e redirecionamentos. Portanto, a abordagem de matriz não será útil nesse caso específico.

Gordon Davisson
fonte
+1 para o evalmétodo. Isso me impediu de fazer a mesma pergunta :). Eu tenho algo assim sudo rsync -aAHXi -n --delete-excluded --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/lost+found","/mnt/DATA/*","/var/log/*","/var/swap","/var/cache/apt/archives/*.deb"} -e ssh root@piac_wireless:/ /home/mrx/Docs/RPi/backup/piac/piac_usb-rootque não estava executando as exclusões corretamente.
dentex
@ Dentex Eu recomendo fortemente contra o uso evalpara isso - há muitas maneiras de dar errado. Uma matriz seria a melhor maneira de fazer isso. Veja aqui , aqui e aqui para exemplos.
Gordon Davisson
Obrigado pela sugestão. Vou tentar implementar a solução com a matriz, para passar a lista de exclusão para o rsync.
dentão
4

Isso deve funcionar:

a="cat -nT index.php |grep 'someregex'"
eval "$a"

Cuidado que evalintroduz vulnerabilidades em potencial e situações inesperadas de comportamento deve ser usado, se houver, com cuidado extra.

jlliagre
fonte
Se você usar eval, realmente precisará usar aspas duplas ( eval "$i") para evitar efeitos extra-extra-estranhos. Por exemplo, tente isso com a="cat -nT index.php |grep ' .* '"(ou seja, a regex deve corresponder a dois espaços com qualquer sequência de caracteres entre eles) e veja o que realmente acontece. Para crédito extra, explique por que isso acontece.
Gordon Davisson
@GordonDavisson aspas duplas adicionadas.
Jlliagre
2

Ao declarar a variável, você não deve usar o cifrão como prefixo.

Tente o seguinte:

i='echo hi'; $i
miq
fonte
11
Está correto, mas na verdade não é uma resposta para a pergunta do OP.
slhck
é para mim REALMENTE: P
nwgat 13/01/2015