【问题标题】:What's the point of eval/bash -c as opposed to just evaluating a variable?eval/bash -c 与仅评估变量相比有什么意义?
【发布时间】:2016-03-29 09:14:12
【问题描述】:

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

COMMAND='echo hello'

有什么区别

$ eval "$COMMAND"
hello

$ bash -c "$COMMAND"
hello

$ $COMMAND
hello

?如果最后一个版本更短并且(据我所见)完全相同,为什么几乎从不使用它?

【问题讨论】:

  • BashFAQ #50 直接就点:mywiki.wooledge.org/BashFAQ/050
  • ...也就是说:它演示了为什么最后一个版本对于非平凡的命令完全被破坏了。
  • 尝试以这种方式运行printf '%s\n' "hello world",例如:您将在一行中看到"hello,然后在第二行看到world"——因为引号不会被解析为语法。

标签: linux bash shell unix sh


【解决方案1】:

第三种形式与其他两种完全不同——但要理解为什么,我们需要进入 bash 在解释命令时的操作顺序,并查看每个方法在使用。

Bash 解析阶段

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

使用eval "$string"

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

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

使用sh -c "$string"

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


使用$string

...从第 5 步“分词”开始。

这是什么意思?

不尊重报价。

printf '%s\n' "two words" 将因此解析为printf %s\n "two words",这与printf %s\n two words 的通常/预期行为相反(引号被外壳消耗)。

不会拆分成多个命令(;s、&s 或类似的)。

因此:

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

...将发出以下输出:

foo && echo bar

...而不是以下,否则会被预期:

foo
bar

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

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

不支持重定向。

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

【讨论】:

    【解决方案2】:
    $ bash -c "$COMMAND"
    

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

    编辑:

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

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

    【讨论】:

    • 启动一个新的 shell 会清除常规的 shell 变量和函数,但 不是 以“新环境”开头;被子进程继承是环境变量的定义特征,而不是常规的 shell 变量。可以使用 env -i bash -c "$command" 来做到这一点,但要确保明确提供 PATH 或完全限定任何路径。
    【解决方案3】:

    至少在某些时候它们是不同的。考虑以下几点:

    $ 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,因为参数扩展和前面的那些步骤已经完成,所以我们不会像使用eval 那样得到var 的值。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-11
      相关资源
      最近更新 更多