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 继承 export
ed 变量,并且由于我没有导出 var
它不适用于新的 shell.
$ bash -c "$COMMAND"
此版本启动一个新的 bash 解释器,运行命令,然后退出,将控制权返回给原始 shell。您根本不需要首先成为 运行 bash 来执行此操作,例如,您可以从 tcsh 启动 bash 解释器。您也可以从 bash 脚本执行此操作,以从一个全新的环境开始或避免污染您当前的环境。
编辑:
正如@CharlesDuffy 指出的那样,以这种方式开始一个新的 bash shell 将清除 shell 变量 但环境变量将由生成的 shell进程.
使用 eval
会导致 shell 解析您的命令两次。在您给出的示例中,直接执行 $COMMAND 或执行 eval
是等效的,但请查看答案 here 以更全面地了解 eval
的好处(或不好)。
第三种形式与其他两种形式完全不同——但要理解原因,我们需要在 bash 解释命令时进入操作顺序,并查看其中哪些是每种方法都在使用中。
Bash 解析阶段
- 报价处理
- 拆分成命令
- 特殊运算符解析
- 扩展
- 分词
- 通配
- 执行
使用eval "$string"
eval "$string"
从#1 开始执行上述所有步骤。因此:
- 字符串中的文字引号变成句法引号
- 处理
>()
等特殊运算符
$foo
等扩展受到尊重
- 这些扩展的结果在字符上被拆分成空格,变成单独的词
- 如果这些词被解析为相同的并且有可用的匹配项,则这些词将扩展为 glob,最后执行命令。
使用sh -c "$string"
...执行与 eval
相同的操作,但 在作为单独进程启动的新 shell 中 ;因此,当这个新进程退出时,对变量状态、当前目录等的更改将过期。 (还要注意,新的 shell 可能是支持不同语言的不同解释器;即 sh -c "foo"
将不支持与 bash
、ksh
、zsh
, 等等.
使用$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)
等
不接受重定向。
>foo
或 2>&1
只是通过字符串拆分创建的另一个词,而不是 shell 指令。
假设您将以下命令存储在变量中:
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 继承 export
ed 变量,并且由于我没有导出 var
它不适用于新的 shell.
$ bash -c "$COMMAND"
此版本启动一个新的 bash 解释器,运行命令,然后退出,将控制权返回给原始 shell。您根本不需要首先成为 运行 bash 来执行此操作,例如,您可以从 tcsh 启动 bash 解释器。您也可以从 bash 脚本执行此操作,以从一个全新的环境开始或避免污染您当前的环境。
编辑:
正如@CharlesDuffy 指出的那样,以这种方式开始一个新的 bash shell 将清除 shell 变量 但环境变量将由生成的 shell进程.
使用 eval
会导致 shell 解析您的命令两次。在您给出的示例中,直接执行 $COMMAND 或执行 eval
是等效的,但请查看答案 here 以更全面地了解 eval
的好处(或不好)。
第三种形式与其他两种形式完全不同——但要理解原因,我们需要在 bash 解释命令时进入操作顺序,并查看其中哪些是每种方法都在使用中。
Bash 解析阶段
- 报价处理
- 拆分成命令
- 特殊运算符解析
- 扩展
- 分词
- 通配
- 执行
使用eval "$string"
eval "$string"
从#1 开始执行上述所有步骤。因此:
- 字符串中的文字引号变成句法引号
- 处理
>()
等特殊运算符 $foo
等扩展受到尊重- 这些扩展的结果在字符上被拆分成空格,变成单独的词
- 如果这些词被解析为相同的并且有可用的匹配项,则这些词将扩展为 glob,最后执行命令。
使用sh -c "$string"
...执行与 eval
相同的操作,但 在作为单独进程启动的新 shell 中 ;因此,当这个新进程退出时,对变量状态、当前目录等的更改将过期。 (还要注意,新的 shell 可能是支持不同语言的不同解释器;即 sh -c "foo"
将不支持与 bash
、ksh
、zsh
, 等等.
使用$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)
等
不接受重定向。
>foo
或 2>&1
只是通过字符串拆分创建的另一个词,而不是 shell 指令。