Variável como comando; eval vs bash -c

41

Eu estava lendo um script que alguém fez e eu observei que o autor não usar eval para avaliar uma variável como um comando
O autor usou

bash -c "$1"

ao invés de

eval "$1"

Presumo que o eval seja o método preferido e, provavelmente, seja mais rápido. Isso é verdade?
Existe alguma diferença prática entre os dois? Quais são as diferenças notáveis ​​entre os dois?

Quem sou eu
fonte
Em algumas ocasiões, você pode fugir sem nenhum dos dois. e='echo foo'; $efunciona muito bem.
Dennis

Respostas:

40

eval "$1"executa o comando no script atual. Ele pode definir e usar variáveis ​​de shell do script atual, definir variáveis ​​de ambiente para o script atual, definir e usar funções do script atual, definir o diretório atual, umask, limites e outros atributos para o script atual e assim por diante. bash -c "$1"executa o comando em um script completamente separado, que herda variáveis ​​de ambiente, descritores de arquivo e outro ambiente de processo (mas não transmite nenhuma alteração de volta), mas não herda configurações internas do shell (variáveis ​​do shell, funções, opções, traps, etc.).

Existe outra maneira, (eval "$1")que executa o comando em um subshell: herda tudo do script de chamada, mas não transmite nenhuma alteração de volta.

Por exemplo, supondo que a variável dirnão seja exportada e $1seja cd "$foo"; ls:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdlista o conteúdo /somewhere/elsee as impressões /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdlista o conteúdo /somewhere/elsee as impressões /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdlista o conteúdo de /starting/directory(porque cd ""não altera o diretório atual) e imprime /starting/directory.
Gilles 'SO- parar de ser mau'
fonte
Obrigado. Eu não conhecia (eval "$ 1"), é diferente da fonte?
whoami
11
@whoami (eval "$1")não tem nada a ver source. É apenas uma combinação de (…)e eval. source fooé aproximadamente equivalente a eval "$(cat foo)".
Gilles 'SO- stop be evil'
Temos de ter sido escrito nossas respostas ao mesmo tempo ...
mikeserv
@ whoami A principal diferença entre evale .doté que evaltrabalha com argumentos e .dottrabalha com arquivos.
precisa saber é o seguinte
Obrigado a vocês dois. Meu comentário anterior parece ser um bocado estúpido agora que eu lê-lo novamente ...
whoami
23

A diferença mais importante entre

bash -c "$1" 

E

eval "$1"

É que o primeiro roda em um subshell e o segundo não. Tão:

set -- 'var=something' 
bash -c "$1"
echo "$var"

SAÍDA:

#there doesn't seem to be anything here
set -- 'var=something' 
eval "$1"
echo "$var"

SAÍDA:

something

Não tenho idéia do porquê alguém usaria o executável bashdessa maneira. Se você precisar chamá-lo, use o built-in garantido POSIX sh. Ou (subshell eval)se você deseja proteger seu ambiente.

Pessoalmente, prefiro o shell .dotacima de tudo.

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0

SAÍDA

something1
something2
something3
something4
something5

MAS VOCÊ PRECISA?

A única causa a ser usada é o fato de sua variável realmente atribuir ou avaliar outra, ou a divisão de palavras é importante para a saída.

Por exemplo:

var='echo this is var' ; $var

SAÍDA:

this is var

Isso funciona, mas apenas porque echonão se importa com a contagem de argumentos.

var='echo "this is var"' ; $var

SAÍDA:

"this is var"

Vejo? As aspas duplas aparecem porque o resultado da expansão de shell $varnão é avaliado quote-removal.

var='printf %s\\n "this is var"' ; $var

SAÍDA:

"this
is
var"

Mas com evalou sh:

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"

SAÍDA:

this is var
this is var

Quando usamos evalou sho shell faz uma segunda passagem nos resultados das expansões e as avalia como um comando potencial também, e assim as aspas fazem a diferença. Você também pode fazer:

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR

SAÍDA

this is var
mikeserv
fonte
5

Eu fiz um teste rápido:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'

(Sim, eu sei, usei o bash -c para executar o loop, mas isso não deve fazer diferença).

Os resultados:

eval    : 1.17s
bash -c : 7.15s

Então evalé mais rápido. Na página do manual de eval:

O utilitário eval deve construir um comando concatenando argumentos juntos, separando cada um com um caractere. O comando construído deve ser lido e executado pelo shell.

bash -cé claro, executa o comando em um shell bash. Uma observação: usei /bin/echoporque echoé um shell interno bash, o que significa que um novo processo não precisa ser iniciado. Substituindo /bin/echopor echopara o bash -cteste, levou 1.28s. Isso é o mesmo. No entanto, evalé mais rápido para executar executáveis. A principal diferença aqui é que evalnão inicia um novo shell (ele executa o comando no atual) enquanto bash -cinicia um novo shell e depois executa o comando no novo shell. Iniciar um novo shell leva tempo, e é por isso que bash -cé mais lento que eval.

PlasmaPower
fonte
Eu acho que o OP quer comparar bash -ccom evalnão exec.
Joseph R.
@JosephR. Opa! Eu vou mudar isso.
PlasmaPower
11
@JosephR. Deve ser corrigido agora. Também eu refiz os testes um pouco mais e bash -cnão é que ruim ...
PlasmaPower
3
Embora isso seja verdade, falta a diferença fundamental de que o comando seja executado em ambientes diferentes. É óbvio que iniciar uma nova instância do bash será mais lento, isso não é uma observação interessante.
Gilles 'SO- stop be evil'