eval
和exec
都内置在执行命令的bash(1)命令中。
我还看到exec
有一些选择,但这是唯一的区别吗?他们的背景发生了什么?
eval
和exec
都内置在执行命令的bash(1)命令中。
我还看到exec
有一些选择,但这是唯一的区别吗?他们的背景发生了什么?
Answers:
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
另一方面,它不会分叉,而只是替换外壳。
相比:
$ 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
可以避免甚至避免错误地混合使用代码和数据。
eval
首先使用的通常免责声明。诸如间接修改变量之类的东西可以通过declare
/ typeset
/ nameref
和诸如${!var}
这样的扩展在许多shell中完成,因此eval
除非我真的必须避免使用,否则我将使用它们代替。
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脚本,将流重定向到单独的文件或进程以及带有文件描述符的其他有趣内容变得非常方便。
至少在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手册指出(我加了强调):
读取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
创建一个新的子进程,运行参数并返回退出状态。
嗯什么 整个问题eval
在于,它不会以任何方式创建子进程。如果我做
eval "cd /tmp"
在shell中,那么之后当前 shell将更改目录。都不会exec
创建新的子进程,而是会为给定的子进程更改当前的可执行文件(即外壳程序)。进程ID(以及打开的文件和其他内容)保持不变。相对于eval
,exec
除非exec
自身由于无法找到或加载可执行文件而失败,或者死于参数扩展问题,否则an 将不会返回到调用shell 。
eval
基本上在连接后将其自变量解释为字符串,即它将做一层额外的通配符扩展和自变量拆分。 exec
没有做那样的事情。
评价
这些工作:
$ 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
exec
)