eval和exec有什么区别?


Answers:


124

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另一方面,它不会分叉,而只是替换外壳。

相比:

$ 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,清单/proc/self列出ls了从shell运行的PID 。通常,进程ID是不同的,但是与exec外壳ls程序相同,并且具有相同的进程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可以避免甚至避免错误地混合使用代码和数据。


这是一个很好的答案,@ ilkkachu。谢谢!
威廉·派肖

有关使用eval的更多详细信息,可以在这里找到:stackoverflow.com/a/46944004/2079103
clearlight

1
@clearlight,好吧,这使我想起该答案中也添加了关于eval首先使用的通常免责声明。诸如间接修改变量之类的东西可以通过declare/ typeset/ nameref和诸如${!var}这样的扩展在许多shell中完成,因此eval除非我真的必须避免使用,否则我将使用它们代替。
ilkkachu

27

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

例如

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

我在这里ksh(我的普通外壳)。我开始bash,然后在bash中exec /bin/echo。我们可以看到,ksh由于此bash过程被替换为,所以我后来又退回去了/bin/echo


脸上的umm掉回了ksh b / c进程,被echo代替了,没有太大意义吗?

14

TL; DR

exec用于使用新命令替换当前的shell进程,并在未指定命令的情况下处理流重定向/文件描述符。eval用于将字符串作为命令求值。两者都可以用来建立和执行带有运行时已知参数的命令,但是exec除了执行命令之外,它还替换了当前shell的进程。

执行内置

句法:

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

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

...替换外壳。没有创建新的过程。自变量成为命令的自变量。

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

这在哪里有用?在称为包装器脚本的东西中。这样的脚本会建立参数集或对将哪些变量传递到环境中做出某些决定,然后用exec指定的命令替换自身,并提供与包装脚本一路建立起来的参数相同的参数。

该手册还指出:

如果未指定command,则任何重定向在当前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

长话短说,存在一个exec使用C编程语言的命令家族,并且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手册指出(我加了强调):

读取arg并将它们串联在一起成为单个命令。然后,该命令由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

@ ctrl-alt-delor我已经编辑了那部分,谢谢,尽管可以说它确实产生了一个新进程,但PID仍然与当前shell相同。以后考虑编辑答案,而不要留下评论和低票,尤其是在对诸如7词短语这样的小问题有疑问时;修改和更正答案的时间要少得多,而且也很有帮助。
Sergiy Kolodyazhnyy,

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

嗯什么 整个问题eval在于,它不会以任何方式创建子进程。如果我做

eval "cd /tmp"

在shell中,那么之后当前 shell将更改目录。都不会exec创建新的子进程,而是会为给定的子进程更改当前的可执行文件(即外壳程序)。进程ID(以及打开的文件和其他内容)保持不变。相对于evalexec除非exec自身由于无法找到或加载可执行文件而失败,或者死于参数扩展问题,否则an 将不会返回到调用shell 。

eval基本上在连接后将其自变量解释为字符串,即它将做一层额外的通配符扩展和自变量拆分。 exec没有做那样的事情。


1
原来的问题是“ eval和exec都是Bash(1)的内置命令,它们执行命令,创建新的子进程,运行参数并返回退出状态”;我已经排除了错误的推定。
查尔斯·斯图尔特

-1

评价

这些工作:

$ 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 $$与子外壳的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

我张贴了这个答案,因为其他的例子并不真正说明这在意志薄弱者如I.充分理解的方式
MATEEN Ulhaq

词语选择不当:流程替换是一个现有概念,似乎与您在此处说明的内容无关。
大师

@muru“流程替换”更好吗?我不确定正确的术语是什么。该联机帮助页似乎将其称为“替换过程映像”。
Mateen Ulhaq

嗯,不知道 (但是,当我听到“替换过程映像”时,我认为:exec
老师
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.