eval
y exec
ambos están integrados en comandos de bash (1) que ejecutan comandos.
También veo que exec
tiene algunas opciones, pero ¿es esa la única diferencia? ¿Qué pasa con su contexto?
eval
y exec
ambos están integrados en comandos de bash (1) que ejecutan comandos.
También veo que exec
tiene algunas opciones, pero ¿es esa la única diferencia? ¿Qué pasa con su contexto?
Respuestas:
eval
y exec
son 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 cmd
hace 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/ls
llamará fork()
para crear un proceso secundario y luego exec()
en el secundario para ejecutar /bin/ls
. exec /bin/ls
por 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/self
nos da el PID del ls
que se ejecutó desde el shell. Por lo general, las ID de proceso son diferentes, pero con exec
el shell y ls
tienen la misma ID de proceso. Además, el siguiente comando exec
no se ejecutó, ya que se reemplazó el shell.
Por otra parte:
$ help eval
eval: eval [arg ...]
Execute arguments as a shell command.
eval
ejecutará los argumentos como un comando en el shell actual. En otras palabras, eval foo bar
es 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/ls
haría un viejo ).
O podríamos tener un comando que genere comandos de shell. La ejecución ssh-agent
inicia 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 ssh
comandos que ejecutaría). Por ssh-agent
lo 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 cmd
contiene 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 eval
es seguro antes de usarla.
A menudo, es posible evitar eval
y evitar incluso mezclar accidentalmente código y datos de manera incorrecta.
eval
en primer lugar a esta respuesta también. Cosas como modificar variables indirectamente se pueden hacer en muchos shells a través de declare
/ typeset
/ nameref
y expansiones como ${!var}
, por lo que usaría esas en lugar de a eval
menos que realmente tuviera que evitarlo.
exec
No 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 bash
y luego dentro de fiesta I exec /bin/echo
. Podemos ver que luego volví a entrar ksh
porque el bash
proceso fue reemplazado por /bin/echo
.
exec
se 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. eval
se usa para evaluar cadenas como comandos. Ambos pueden usarse para construir y ejecutar un comando con argumentos conocidos en tiempo de ejecución, pero exec
reemplaza el proceso del shell actual además de ejecutar comandos.
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 bash
con PID 1234 y si tuviera que ejecutar exec top -u root
dentro de ese shell, el top
comando 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 exec
para 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 stdout
comandos, 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 bash
versión 4.3, el exec
incorporado 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.c
archivo.
Para resumir, existe una familia de exec
comandos 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;
{
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 exec
donde el objetivo es simular la execve()
funcionalidad, la función eval
incorporada 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.def
y pasa la cadena de entrada analizada a evalstring()
funcionar.
Entre otras cosas, eval
puede asignar variables que permanecen en el entorno actual de ejecución de shell, mientras exec
que no puede:
$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
creando un nuevo proceso hijo, ejecute los argumentos y devuelva el estado de salida.
¿Cómo? El punto eval
es 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 exec
crea 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 exec
no volverá al shell de llamada a menos que el exec
mismo falle debido a que no puede encontrar o cargar el ejecutable o morir por problemas de expansión de argumentos.
eval
bá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. exec
no hace nada de eso
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 exec
reemplaza 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, eval
no 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
exec
)