Variável como comando; eval vs bash -c

38

Eu estava lendo um roteiro de bash que alguém fez e notei que o autor não usa eval para avaliar uma variável como um comando
O autor usou

bash -c "$1"

em vez de

eval "$1"

Eu assumo que usar o eval é o método preferido e provavelmente é mais rápido de qualquer maneira. Isso é verdade?
Existe alguma diferença prática entre os dois? Quais são as diferenças notáveis entre os dois?

    
por whoami 14.04.2014 / 00:05

3 respostas

39

eval "$1" executa o comando no script atual. Ele pode definir e usar variáveis de shell a partir 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), mas não herda as configurações internas do shell (variáveis de shell, funções, opções, traps, etc) .).

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

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

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwd lista o conteúdo de /somewhere/else e imprime /somewhere/else .
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwd lista o conteúdo de /somewhere/else e imprime /starting/directory .
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwd lista o conteúdo de /starting/directory (porque cd "" não altera o diretório atual) e imprime /starting/directory .
por 14.04.2014 / 00:47
23

A diferença mais importante entre

bash -c "$1" 

e

eval "$1"

É que o primeiro é executado em uma subcamada e o segundo não. Então:

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

OUTPUT:

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

OUTPUT:

something

Não tenho a menor idéia de por que alguém usaria o executável bash dessa maneira. Se você precisar invocá-lo, use o sh interno POSIX garantido. Ou (subshell eval) se você deseja proteger seu ambiente.

Pessoalmente, eu prefiro o .dot do shell acima de tudo.

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

OUTPUT

something1
something2
something3
something4
something5

MAS PRECISA DE TODOS?

A única causa para usar, na verdade, é no caso 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

OUTPUT:

this is var

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

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

OUTPUT:

"this is var"

Veja? Aspas duplas aparecem porque o resultado da expansão do shell de $var não é avaliado para quote-removal .

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

OUTPUT:

"this
is
var"

Mas com eval ou sh :

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

OUTPUT:

this is var
this is var

Quando usamos eval ou sh , o shell faz uma segunda passagem nos resultados das expansões e também as avalia como um comando em potencial, e assim as cotações fazem a diferença. Você também pode fazer:

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

OUTPUT

this is var
    
por 14.04.2014 / 00:54
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

Portanto, eval é mais rápido. Na página man de eval :

The eval utility shall construct a command by concatenating arguments together, separating each with a character. The constructed command shall be read and executed by the shell.

bash -c , claro, executa o comando em um shell bash. Uma nota: usei /bin/echo porque echo é um shell integrado com bash , o que significa que um novo processo não precisa ser iniciado. Substituindo /bin/echo por echo para o teste bash -c , demorou 1.28s . Isso é quase o mesmo. Hovever, eval é mais rápido para executar executáveis. A principal diferença aqui é que eval não inicia um novo shell (ele executa o comando no atual), enquanto bash -c inicia um novo shell e executa o comando no novo shell. Iniciar um novo shell leva tempo, e é por isso que bash -c é mais lento que eval .

    
por 14.04.2014 / 00:28