Variable como comando; eval vs bash -c

41

Estaba leyendo un script bash alguien hizo y me di cuenta de que el autor no utiliza eval para evaluar una variable como un comando
, el autor usa

bash -c "$1"

en lugar de

eval "$1"

Supongo que usar eval es el método preferido y probablemente sea más rápido de todos modos. ¿Es eso cierto?
¿Hay alguna diferencia práctica entre los dos? ¿Cuáles son las diferencias notables entre los dos?

quién soy
fuente
En algunas ocasiones, puedes escapar sin ninguno de los dos. e='echo foo'; $efunciona bien
Dennis

Respuestas:

40

eval "$1"ejecuta el comando en el script actual. Puede establecer y usar variables de shell del script actual, establecer variables de entorno para el script actual, establecer y usar funciones del script actual, establecer el directorio actual, umask, límites y otros atributos para el script actual, y así sucesivamente. bash -c "$1"ejecuta el comando en un script completamente separado, que hereda las variables de entorno, los descriptores de archivo y otro entorno de proceso (pero no transmite ningún cambio) pero no hereda la configuración interna del shell (variables del shell, funciones, opciones, trampas, etc.).

Hay otra forma, (eval "$1")que ejecuta el comando en una subshell: hereda todo del script de llamada pero no transmite ningún cambio.

Por ejemplo, suponiendo que la variable dirno se exporta y $1es cd "$foo"; ls, entonces:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdenumera el contenido /somewhere/elsey las impresiones /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdenumera el contenido /somewhere/elsey las impresiones /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdenumera el contenido de /starting/directory(porque cd ""no cambia el directorio actual) e imprime /starting/directory.
Gilles 'SO- deja de ser malvado'
fuente
Gracias. No sabía sobre (eval "$ 1"), ¿es diferente de la fuente?
whoami
1
@whoami (eval "$1")no tiene nada que ver con eso source. Es solo una combinación de (…)y eval. source fooes más o menos equivalente a eval "$(cat foo)".
Gilles 'SO- deja de ser malvado'
Debemos haber estado escribiendo nuestras respuestas al mismo tiempo ...
mikeserv
@whoami La principal diferencia entre evaly .dotes que evalfunciona con argumentos y .dotfunciona con archivos.
mikeserv
Gracias a los dos. Mi comentario anterior parece ser un poco estúpido ahora que lo leí de nuevo ...
whoami
23

La diferencia más importante entre

bash -c "$1" 

Y

eval "$1"

Es que el primero se ejecuta en una subshell y el segundo no. Asi que:

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

SALIDA:

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

SALIDA:

something

Sin embargo, no tengo idea de por qué alguien usaría el ejecutable bashde esa manera. Si debe invocarlo, utilice el POSIX garantizado incorporado sh. O (subshell eval)si desea proteger su medio ambiente.

Personalmente, prefiero el caparazón .dotsobre todo.

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

SALIDA

something1
something2
something3
something4
something5

¿PERO LO NECESITA?

La única causa para usar, en realidad, es en el caso de que su variable realmente asigne o evalúe a otra, o la división de palabras sea importante para la salida.

Por ejemplo:

var='echo this is var' ; $var

SALIDA:

this is var

Eso funciona, pero solo porque echono le importa su conteo de argumentos.

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

SALIDA:

"this is var"

¿Ver? Las comillas dobles aparecen porque $varno se evalúa el resultado de la expansión de la shell quote-removal.

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

SALIDA:

"this
is
var"

Pero con evalo sh:

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

SALIDA:

this is var
this is var

Cuando usamos evalo shel shell da un segundo paso a los resultados de las expansiones y también los evalúa como un comando potencial, por lo que las citas marcan la diferencia. También puedes hacer:

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

SALIDA

this is var
mikeserv
fuente
5

Hice una prueba rápida:

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'

(Sí, lo sé, usé bash -c para ejecutar el bucle, pero eso no debería hacer la diferencia).

Los resultados:

eval    : 1.17s
bash -c : 7.15s

Entonces evales más rápido. De la página del manual de eval:

La utilidad eval construirá un comando concatenando argumentos juntos, separando cada uno con un carácter. El comando construido será leído y ejecutado por el shell.

bash -cpor supuesto, ejecuta el comando en un shell bash. Una nota: utilicé /bin/echoporque echoes un shell incorporado bash, lo que significa que no es necesario iniciar un nuevo proceso. Sustitución /bin/echode echola bash -cprueba, se tomó 1.28s. Eso es casi lo mismo. Hovever, evales más rápido para ejecutar ejecutables. La diferencia clave aquí es que evalno inicia un nuevo shell (ejecuta el comando en el actual) mientras que bash -cinicia un nuevo shell, luego ejecuta el comando en el nuevo shell. Iniciar un nuevo shell lleva tiempo, y es por eso que bash -ces más lento que eval.

PlasmaPower
fuente
Creo que el OP quiere comparar bash -ccon evalno exec.
Joseph R.
@JosephR. ¡Uy! Voy a cambiar eso
PlasmaPower
1
@JosephR. Debería arreglarse ahora. También hice de nuevo las pruebas un poco más y bash -cno es que mal ...
PlasmaPower
3
Si bien esto es cierto, se pierde la diferencia fundamental de que el comando se ejecuta en diferentes entornos. Es obvio que comenzar una nueva instancia de bash será más lento, esta no es una observación interesante.
Gilles 'SO- deja de ser malvado'