eval 和 exec 有什么区别?

eval 和 exec 有什么区别?

evalexec都是内置于 bash(1) 执行命令的命令中。

我还看到exec有几个选项,但这是唯一的区别吗?他们的背景会发生什么?

答案1

evalexec完全不同的野兽。 (除了两者都会运行命令这一事实之外,您在 shell 中执行的所有操作也是如此。)

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

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

比较:

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

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

echo $$打印我启动的 shell 的 PID,列表为我们提供了从 shell 运行的/proc/selfPID 。ls通常,进程ID是不同的,但与execshellls具有相同的进程ID。此外,exec由于外壳已被替换,因此以下命令未运行。


另一方面:

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

eval将在当前 shell 中将参数作为命令运行。换句话说,eval foo bar与 just 相同foo bar。但是变量在执行之前会被扩展,因此我们可以执行保存在shell变量中的命令:

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

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

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

eval $(ssh-agent)

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


当然,如果变量cmd碰巧包含类似 的内容rm -rf $HOME,那么运行eval "$cmd"就不是您想要做的事情。甚至像字符串内的命令替换之类的事情也会被处理,所以一个应该真的eval使用前请确保输入是安全的。

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

答案2

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

例如

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

我在这里ksh(我的正常外壳)。我开始bash,然后在 bash I 内exec /bin/echo。我们可以看到我后来又被退回了,ksh因为该bash过程被替换为/bin/echo.

答案3

长话短说

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

执行内置

句法:

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

根据手册是否有指定此内置命令

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

换句话说,如果您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

这种行为很方便登录 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;
{

评估内置

bash 4.3 手册指出(重点是我添加的):

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

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

这可能在哪里有用?正如吉尔斯指出的在这个答案中,“...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

答案4

评估

这些工作:

$ echo hi
hi

$ eval "echo hi"
hi

$ exec echo hi
hi

然而,这些并没有:

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

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

处理图像替换

此示例演示如何exec替换其调用进程的图像:

# 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

请注意,它exec echo $$是使用子 shell 的 PID 运行的!此外,完成后,我们又回到了原来的sh$外壳中。

另一方面,eval是否不是替换过程映像。相反,它会像通常在 shell 本身中一样运行给定的命令。 (当然,如果您运行一个需要生成进程的命令......它就是这样做的!)

sh$ echo $$
1234

sh$ sh

sh-subshell$ echo $$
5678

sh-subshell$ eval echo $$
5678

# We are still in the subshell!
sh-subshell$ echo $$
5678

相关内容