¿Cuál es la diferencia entre eval y exec?

Respuestas:

124

evaly execson bestias completamente diferentes. (Aparte del hecho de que ambos ejecutarán comandos, pero también todo lo que haces en un shell).

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

Lo que exec cmdhace es exactamente lo mismo que solo ejecutar cmd, excepto que el shell actual se reemplaza con el comando, en lugar de que se ejecute un proceso separado. Internamente, ejecutar say /bin/lsllamará fork()para crear un proceso secundario y luego exec()en el secundario para ejecutar /bin/ls. exec /bin/lspor otro lado no se bifurcará, sino que simplemente reemplazará la carcasa.

Comparar:

$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo

con

$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217

echo $$imprime el PID del shell que comencé, y el listado /proc/selfnos da el PID del lsque se ejecutó desde el shell. Por lo general, las ID de proceso son diferentes, pero con execel shell y lstienen la misma ID de proceso. Además, el siguiente comando execno se ejecutó, ya que se reemplazó el shell.


Por otra parte:

$ help eval
eval: eval [arg ...]
    Execute arguments as a shell command.

evalejecutará los argumentos como un comando en el shell actual. En otras palabras, eval foo bares lo mismo que justo foo bar. Pero las variables se expandirán antes de la ejecución, por lo que podemos ejecutar comandos guardados en variables de shell:

$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo

Será no crear un proceso hijo, por lo que la variable se establece en el shell actual. (Por supuesto eval /bin/ls, creará un proceso hijo, de la misma manera que lo /bin/lsharía un viejo ).

O podríamos tener un comando que genere comandos de shell. La ejecución ssh-agentinicia el agente en segundo plano y genera un montón de asignaciones variables, que podrían establecerse en el shell actual y ser utilizadas por los procesos secundarios (los sshcomandos que ejecutaría). Por ssh-agentlo tanto, se puede comenzar con:

eval $(ssh-agent)

Y el shell actual obtendrá las variables para que otros comandos hereden.


Por supuesto, si la variable cmdcontiene algo así rm -rf $HOME, entonces ejecutar eval "$cmd"no sería algo que querría hacer. Incluso cosas como sustituciones de mando dentro de la cadena serían procesados, por lo que uno debe realmente estar seguro de que la entrada evales seguro antes de usarla.

A menudo, es posible evitar evaly evitar incluso mezclar accidentalmente código y datos de manera incorrecta.

ilkkachu
fuente
Esa es una gran respuesta, @ilkkachu. ¡Gracias!
Willian Paixao
Puede encontrar más detalles sobre el uso de eval aquí: stackoverflow.com/a/46944004/2079103
clearlight
1
@clearlight, bueno, eso me recuerda agregar el descargo de responsabilidad habitual sobre no usar evalen primer lugar a esta respuesta también. Cosas como modificar variables indirectamente se pueden hacer en muchos shells a través de declare/ typeset/ namerefy expansiones como ${!var}, por lo que usaría esas en lugar de a evalmenos que realmente tuviera que evitarlo.
ilkkachu
27

execNo crea un nuevo proceso. Se reemplaza el proceso actual con el nuevo comando. Si hizo esto en la línea de comando, finalizará efectivamente su sesión de shell (¡y tal vez cierre la sesión o cierre la ventana de terminal!)

p.ej

ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh% 

Aquí estoy ksh(mi caparazón normal). Comienzo bashy luego dentro de fiesta I exec /bin/echo. Podemos ver que luego volví a entrar kshporque el bashproceso fue reemplazado por /bin/echo.

Stephen Harris
fuente
umm en la cara caída de nuevo en ksh b / c proceso fue reemplazado por echo ¿no tiene mucho sentido?
14

TL; DR

execse usa para reemplazar el proceso actual del shell con nuevo y manejar la redirección de flujo / descriptores de archivo si no se ha especificado ningún comando. evalse usa para evaluar cadenas como comandos. Ambos pueden usarse para construir y ejecutar un comando con argumentos conocidos en tiempo de ejecución, pero execreemplaza el proceso del shell actual además de ejecutar comandos.

ejecutivo incorporado

Sintaxis:

exec [-cl] [-a name] [command [arguments]]

Según el manual, si hay un comando especificado, este incorporado

... reemplaza el caparazón. No se crea ningún proceso nuevo. Los argumentos se convierten en argumentos para ordenar.

En otras palabras, si estaba ejecutando bashcon PID 1234 y si tuviera que ejecutar exec top -u rootdentro de ese shell, el topcomando tendrá PID 1234 y reemplazará su proceso de shell.

¿Dónde es esto útil? En algo conocido como scripts de envoltura. Tales scripts crean conjuntos de argumentos o toman ciertas decisiones sobre qué variables pasar al entorno, y luego se usan execpara reemplazarse con cualquier comando que se especifique, y, por supuesto, proporcionan esos mismos argumentos que el script de envoltura ha desarrollado a lo largo del camino.

Lo que también dice el manual es que:

Si no se especifica el comando, cualquier redirección tendrá efecto en el shell actual

Esto nos permite redirigir cualquier cosa desde las secuencias de salida de shells actuales a un archivo. Esto puede ser útil para fines de registro o filtrado, donde no desea ver los stdoutcomandos, sino solo stderr. Por ejemplo, así:

bash-4.3$ exec 3>&1
bash-4.3$ exec > test_redirect.txt
bash-4.3$ date
bash-4.3$ echo "HELLO WORLD"
bash-4.3$ exec >&3
bash-4.3$ cat test_redirect.txt 
2017 05 20 星期六 05:01:51 MDT
HELLO WORLD

Este comportamiento lo hace útil para iniciar sesión en scripts de shell , redirigir secuencias a archivos o procesos separados y otras cosas divertidas con descriptores de archivo.

En el nivel del código fuente, al menos para la bashversión 4.3, el execincorporado está definido en builtins/exec.def. Analiza los comandos recibidos, y si hay alguno, pasa cosas a la shell_execve()función definida en el execute_cmd.carchivo.

Para resumir, existe una familia de execcomandos en lenguaje de programación C, y shell_execve()es básicamente una función de envoltura de execve:

/* Call execve (), handling interpreting shell scripts, and handling
   exec failures. */
int
shell_execve (command, args, env)
     char *command;
     char **args, **env;
{

eval incorporado

Los estados del manual bash 4.3 (énfasis agregado por mí):

Los argumentos se leen y se concatenan juntos en un solo comando. Luego, el shell lee y ejecuta este comando , y su estado de salida se devuelve como el valor de eval.

Tenga en cuenta que no se produce un reemplazo del proceso. A diferencia de execdonde el objetivo es simular la execve()funcionalidad, la función evalincorporada solo sirve para "evaluar" argumentos, como si el usuario los hubiera escrito en la línea de comandos. Como tal, se crean nuevos procesos.

¿Dónde podría ser útil? Como Gilles señaló en esta respuesta , "... eval no se usa muy a menudo. En algunos shells, el uso más común es obtener el valor de una variable cuyo nombre no se conoce hasta el tiempo de ejecución". Personalmente, lo he usado en un par de secuencias de comandos en Ubuntu donde era necesario ejecutar / evaluar un comando basado en el espacio de trabajo específico que el usuario estaba usando actualmente.

En el nivel del código fuente, se define builtins/eval.defy pasa la cadena de entrada analizada a evalstring()funcionar.

Entre otras cosas, evalpuede asignar variables que permanecen en el entorno actual de ejecución de shell, mientras execque no puede:

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
Sergiy Kolodyazhnyy
fuente
@ ctrl-alt-delor He editado esa parte, gracias, aunque posiblemente genere un nuevo proceso, el PID simplemente sigue siendo el mismo que el del shell actual. En el futuro, considere editar las respuestas, en lugar de dejar un comentario y un voto negativo, especialmente cuando se trata de una frase menor de 7 palabras; editar y corregir una respuesta lleva mucho menos tiempo, y es mucho más útil.
Sergiy Kolodyazhnyy
5
creando un nuevo proceso hijo, ejecute los argumentos y devuelva el estado de salida.

¿Cómo? El punto evales que de ninguna manera crea un proceso hijo. Si lo hago

eval "cd /tmp"

en un shell, luego el shell actual habrá cambiado de directorio. Tampoco execcrea un nuevo proceso hijo, sino que cambia el ejecutable actual (es decir, el shell) para el dado; la identificación del proceso (y los archivos abiertos y otras cosas) permanecen igual. A diferencia de eval, un execno volverá al shell de llamada a menos que el execmismo falle debido a que no puede encontrar o cargar el ejecutable o morir por problemas de expansión de argumentos.

evalbásicamente interpreta sus argumentos como una cadena después de la concatenación, es decir, hará una capa adicional de expansión de comodines y división de argumentos. execno hace nada de eso

usuario180452
fuente
1
La pregunta original decía "eval y exec son comandos integrados de Bash (1) y ejecuta comandos, crea un nuevo proceso hijo, ejecuta los argumentos y devuelve el estado de salida"; He editado la presunción falsa.
Charles Stewart
-1

Evaluación

Estos trabajan:

$ echo hi
hi

$ eval "echo hi"
hi

$ exec echo hi
hi

Sin embargo, estos no:

$ exec "echo hi"
bash: exec: echo hi: not found

$ "echo hi"
bash: echo hi: command not found

Procesar reemplazo de imagen

Este ejemplo demuestra cómo execreemplaza la imagen de su proceso de llamada:

# Get PID of current shell
sh$ echo $$
1234

# Enter a subshell with PID 5678
sh$ sh

# Check PID of subshell
sh-subshell$ echo $$
5678

# Run exec
sh-subshell$ exec echo $$
5678

# We are back in our original shell!
sh$ echo $$
1234

¡Observe que se exec echo $$ejecutó con el PID de la subshell! Además, después de que se completó, volvimos a nuestro sh$shell original .

Por otro lado, evalno no sustituir la imagen del proceso. Más bien, ejecuta el comando dado como lo haría normalmente dentro del shell. (Por supuesto, si ejecuta un comando que requiere que se genere un proceso ... ¡lo hace!)

sh$ echo $$
1234

sh$ sh

sh-subshell$ echo $$
5678

sh-subshell$ eval echo $$
5678

# We are still in the subshell!
sh-subshell$ echo $$
5678
Mateen Ulhaq
fuente
Publiqué esta respuesta porque los otros ejemplos realmente no ilustran esto de una manera suficientemente comprensible para mentes débiles como yo.
Mateen Ulhaq
Mala elección de palabras: la sustitución de procesos es un concepto existente que parece no estar relacionado con lo que sea que esté ilustrando aquí.
Muru
@muru ¿Es mejor el "reemplazo de procesos"? No estoy seguro de cuál es la terminología correcta. La página de manual parece llamarlo "reemplazar la imagen del proceso".
Mateen Ulhaq
Hmm, no sé. (Pero cuando escucho "reemplazando la imagen del proceso", pienso: exec)
Muru