什么是eval / bash -c,而不是仅仅评估一个variables?

假设你有下面的命令存储在一个variables中:

COMMAND='echo hello' 

有什么区别

 $ eval "$COMMAND" hello $ bash -c "$COMMAND" hello $ $COMMAND hello 

? 为什么最后的版本几乎从来没有使用,如果它更短,(据我所知)做的是完全一样的东西?

第三种形式完全不同于其他两种 – 但是为了理解为什么,当解释一个命令时,我们需要进入操作的顺序,并且看看每种方法在使用时要遵循哪一个。

Bash解析阶段

  1. 报价处理
  2. 分裂成命令
  3. 特殊的操作符解析
  4. 展开
  5. 分词
  6. 通配符
  7. 执行

使用eval "$string"

eval "$string"遵循从#1开始的所有上述步骤。 从而:

  • 字符串中的文字引号变为语法引号
  • 特殊的运算符如>()被处理
  • 诸如$foo这样的扩展是很荣幸的
  • 这些扩展的结果被分割成单独的字符的空白字符
  • 这些单词如果解析为相同的并且具有可用的匹配则扩展为球体,最后执行该命令。

使用sh -c "$string"

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


使用$string

…从步骤5开始,“分词”。

这是什么意思?

行情不受尊重。

printf '%s\n' "two words"将因此解析为printf %s\n "two words" ,而不是printf %s\n two words的常见/预期行为)。

分割成多个命令(on ; s, & s或类似的)不会发生。

从而:

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

…将发出以下输出:

 foo && echo bar 

…而不是以下,否则会发生:

 foo bar 

特殊的运营商和扩张不被尊重。

没有$(foo) ,没有$foo ,没有<(foo)

重定向不被尊重。

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

 $ bash -c "$COMMAND" 

这个版本启动一个新的bash解释器,运行命令,然后退出,将控制返回到原来的shell。 例如,你不需要首先运行bash,你可以从tcsh启动一个bash解释器。 您也可以使用bash脚本来创建新环境,或避免污染当前的环境。

编辑:

正如@CharlesDuffy指出的那样以这种方式启动一个新的bash shell将会清除shell变量, 但是环境变量将会被衍生的shell进程继承。

使用eval会导致shell解析你的命令两次。 在你给出的例子中,直接执行$ COMMAND或者执行一个eval是等价的,但是在这里查看答案可以更全面的了解eval是好还是不好。

至少有不同的时候。 考虑以下:

 $ 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 

我们可以在这里看到查尔斯·达菲在他杰出的答案中谈到的很多东西。 例如,试图直接执行变量会打印$var因为参数扩展和那些先前的步骤已经完成了,所以我们没有得到var的值,就像我们用eval

bash -c选项只能从父shell中继承export ed变量,因为我没有导出var所以它不可用于新的shell。