eval
和exec
都是内置于 bash(1) 执行命令的命令中。
我还看到exec
有几个选项,但这是唯一的区别吗?他们的背景会发生什么?
答案1
eval
是exec
完全不同的野兽。 (除了两者都会运行命令这一事实之外,您在 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/ls
。exec /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/self
PID 。ls
通常,进程ID是不同的,但与exec
shellls
具有相同的进程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 脚本,将流重定向到单独的文件或流程, 和别的好玩的东西与文件描述符。
至少在bash
4.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 手册指出(重点是我添加的):
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