eval/bash -c 与仅评估变量相比有什么意义?

What's the point of eval/bash -c as opposed to just evaluating a variable?

假设您将以下命令存储在变量中:

COMMAND='echo hello'

有什么区别

$ eval "$COMMAND"
hello

$ bash -c "$COMMAND"
hello

$ $COMMAND
hello

?如果最后一个版本更短并且(据我所知)做的事情完全相同,为什么几乎从未使用过它?

至少有几次他们是不同的。考虑以下因素:

$ cmd="echo $var"
$ var=hello
$ $cmd
$var
$ eval $cmd
hello
$ bash -c "$cmd"

$ var=world bash -c "$cmd"
world

显示执行变量扩展的不同点。如果先set -x

就更清楚了
$ set -x
$ $cmd
+ echo '$var'
$var
$ eval $cmd
+ eval echo '$var'
++ echo hello
hello
$ bash -c "$cmd"
+ bash -c 'echo $var'

$ var=world bash -c "$cmd"
+ var=world
+ bash -c 'echo $var'
world

我们可以在这里看到 Charles Duffy 在他出色的回答中谈到的大部分内容。例如,尝试直接执行变量会打印 $var,因为参数扩展和前面的步骤已经完成,所以我们不会像 [=15] 那样获取 var 的值=].

bash -c 选项仅从父 shell 继承 exported 变量,并且由于我没有导出 var 它不适用于新的 shell.

$ bash -c "$COMMAND"

此版本启动一个新的 bash 解释器,运行命令,然后退出,将控制权返回给原始 shell。您根本不需要首先成为 运行 bash 来执行此操作,例如,您可以从 tcsh 启动 bash 解释器。您也可以从 bash 脚本执行此操作,以从一个全新的环境开始或避免污染您当前的环境。

编辑:

正如@CharlesDuffy 指出的那样,以这种方式开始一个新的 bash shell 将清除 shell 变量 但环境变量将由生成的 shell进程.

使用 eval 会导致 shell 解析您的命令两次。在您给出的示例中,直接执行 $COMMAND 或执行 eval 是等效的,但请查看答案 here 以更全面地了解 eval 的好处(或不好)。

第三种形式与其他两种形式完全不同——但要理解原因,我们需要在 bash 解释命令时进入操作顺序,并查看其中哪些是每种方法都在使用中。

Bash 解析阶段

  1. 报价处理
  2. 拆分成命令
  3. 特殊运算符解析
  4. 扩展
  5. 分词
  6. 通配
  7. 执行

使用eval "$string"

eval "$string" 从#1 开始执行上述所有步骤。因此:

  • 字符串中的文字引号变成句法引号
  • 处理>()等特殊运算符
  • $foo 等扩展受到尊重
  • 这些扩展的结果在字符上被拆分成空格,变成单独的词
  • 如果这些词被解析为相同的并且有可用的匹配项,则这些词将扩展为 glob,最后执行命令。

使用sh -c "$string"

...执行与 eval 相同的操作,但 在作为单独进程启动的新 shell 中 ;因此,当这个新进程退出时,对变量状态、当前目录等的更改将过期。 (还要注意,新的 shell 可能是支持不同语言的不同解释器;即 sh -c "foo" 将不支持与 bashkshzsh, 等等.


使用$string

...从第 5 步开始,"Word Splitting"。

这是什么意思?

报价不兑现。

printf '%s\n' "two words" 将因此解析为 printf %s\n "two words",而不是 [=25= 的 usual/expected 行为] %s\n two words(引号被 shell 使用)。

拆分为多个命令(在 ;s、&s 或类似时间)不会发生。

因此:

s='echo foo && echo bar'
$s

...将发出以下输出:

foo && echo bar

...而不是预期的以下内容:

foo
bar

不支持特殊运算符和扩展。

$(foo)、无$foo、无<(foo)

不接受重定向。

>foo2>&1 只是通过字符串拆分创建的另一个词,而不是 shell 指令。