eval 和 exec 有什么区别?

Wil*_*xao 100 shell bash shell-builtin

eval并且exec都内置在执行命令的 bash(1) 命令中。

我也看到exec有几个选项,但这是唯一的区别吗?他们的上下文会发生什么?

ilk*_*chu 161

eval并且exec是完全不同的野兽。(除了两者都将运行命令之外,您在 shell 中所做的一切也是如此。)

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.
Run Code Online (Sandbox Code Playgroud)

做什么exec cmd,与只运行完全相同cmd,除了当前的 shell 被替换为命令,而不是运行一个单独的进程。在内部,运行 say/bin/ls会调用fork()创建子进程,然后exec()在子进程中执行/bin/lsexec /bin/ls另一方面不会分叉,而只是替换外壳。

相比:

$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo
Run Code Online (Sandbox Code Playgroud)

$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217
Run Code Online (Sandbox Code Playgroud)

echo $$打印我启动的 shell 的 PID,listing/proc/self为我们提供ls了从 shell 运行的 PID 。通常,进程ID 不同,但与execshell 并ls具有相同的进程ID。此外,下面的命令exec没有运行,因为外壳被替换了。


另一方面:

$ help eval
eval: eval [arg ...]
    Execute arguments as a shell command.
Run Code Online (Sandbox Code Playgroud)

eval将在当前 shell 中将参数作为命令运行。换句话说eval foo bar就是和 just 一样foo bar。但是变量在执行前会被展开,所以我们可以执行保存在shell变量中的命令:

$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo
Run Code Online (Sandbox Code Playgroud)

不会创建子进程,因此该变量是在当前 shell 中设置的。(当然eval /bin/ls会创建一个子进程,就像一个普通的旧进程一样/bin/ls。)

或者我们可以有一个输出 shell 命令的命令。运行ssh-agent在后台启动代理,并输出一堆变量赋值,这些变量可以在当前 shell 中设置并由子进程(ssh您将运行的命令)使用。因此ssh-agent可以从以下开始:

eval $(ssh-agent)
Run Code Online (Sandbox Code Playgroud)

并且当前shell会获取其他命令继承的变量。


当然,如果变量cmd碰巧包含类似的内容rm -rf $HOME,那么运行eval "$cmd"将不是您想要做的事情。喜欢里面的字符串命令替换即便事情会被处理,所以应该真正肯定的是,输入eval是在使用它之前是安全的。

通常,eval可以避免甚至避免以错误的方式意外地混合代码和数据。


Ste*_*ris 30

exec不创建新进程。它新命令替换当前进程。如果您在命令行上执行此操作,那么它将有效地结束您的 shell 会话(并且可能将您注销或关闭终端窗口!)

例如

ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh% 
Run Code Online (Sandbox Code Playgroud)

我在这里ksh(我的普通外壳)。我开始bash,然后在 bash I 里面exec /bin/echo。我们可以看到我ksh后来又被退回了,因为这个bash过程被/bin/echo.


Ser*_*nyy 17

TL; 博士

exec如果没有指定命令,则用于用新的和处理流重定向/文件描述符替换当前的 shell 进程。eval用于将字符串评估为命令。两者都可用于构建和执行带有运行时已知参数的命令,但exec除了执行命令外还替换当前 shell 的进程。

执行内置

句法:

exec [-cl] [-a name] [command [arguments]]
Run Code Online (Sandbox Code Playgroud)

根据手册,如果有命令指定这个内置

...替换外壳。没有创建新进程。参数成为命令的参数。

换句话说,如果您bash使用 PID 1234运行并且要exec top -u root在该 shell 中运行,则该top命令将具有 PID 1234 并替换您的 shell 进程。

这有什么用?在称为包装脚本的东西中。此类脚本构建参数集或做出关于将哪些变量传递到环境中的某些决定,然后使用exec指定的任何命令替换自身,当然还提供包装器脚本在此过程中构建的相同参数。

手册还指出:

如果未指定命令,则任何重定向都在当前 shell 中生效

这允许我们将当前 shell 输出流中的任何内容重定向到文件中。这对于记录或过滤目的可能很有用,在这种情况下您不想看到stdout命令而只想看到stderr. 例如,像这样:

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
Run Code Online (Sandbox Code Playgroud)

这种行为使得登录 shell 脚本、将流重定向到单独的文件或进程以及其他带有文件描述符的有趣内容变得非常方便。

至少在bash4.3 版的源代码级别, exec内置定义在builtins/exec.def. 它解析接收到的命令,如果有的话,它会将内容传递给文件中shell_execve()定义的函数execute_cmd.c

长话短说,execC 编程语言中存在一系列命令,shell_execve()基本上是以下的包装函数execve

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

评估内置

bash 4.3 手册说明(重点是我加的):

args 被读取并连接在一起成为一个命令。然后这个命令被shell读取并 执行,它的退出状态作为 eval 的值返回。

请注意,没有发生进程替换。与exec目标是模拟execve()功能不同,eval内置函数仅用于“评估”参数,就像用户在命令行上输入它们一样。因此,创建了新进程。

这可能在哪里有用?正如 Gilles在此答案中指出的那样,“...eval 不经常使用。在某些 shell 中,最常见的用途是获取名称直到运行时才知道的变量的值”。就我个人而言,我在 Ubuntu 上的几个脚本中使用了它,在这些脚本中,有必要根据用户当前使用的特定工作区执行/评估命令。

在源代码级别,它被定义在builtins/eval.def并将解析的输入字符串传递给evalstring()函数。

除其他外,eval可以分配保留在当前 shell 执行环境中的变量,而exec不能:

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
Run Code Online (Sandbox Code Playgroud)


小智 5

创建一个新的子进程,运行参数并返回退出状态。

呃什么?重点eval是它不会以任何方式创建子进程。如果我做

eval "cd /tmp"
Run Code Online (Sandbox Code Playgroud)

在 shell 中,然后当前shell 将更改目录。也不exec创建新的子进程,而是更改给定的当前可执行文件(即 shell);进程 ID(以及打开的文件和其他内容)保持不变。与 相反eval, anexec不会返回到调用 shell,除非它exec本身由于无法找到或加载可执行文件或死于参数扩展问题而失败。

eval基本上将其参数解释为连接后的字符串,即它将执行额外的通配符扩展和参数拆分层。 exec不会做类似的事情。