$ echo $(echo x; echo y)
x y
$ a='echo x; echo y'
$ echo $($a) # expect 'x y'
x; echo y
- 为什么命令替换会有这样的行为?
- 如何在不使用
eval
and 的情况下对存储在变量中的命令列表执行命令替换bash -c
?
echo $(eval $a)
实际上echo $(bash -c "$a")
做了我想做的事情,但我听说使用 eval 通常是解决问题的错误方法,所以我想知道如何在没有这些命令的情况下进行管理(使用bash -c
实际上是同一件事)
答案1
分词在命令的评估中发生得很晚。对你来说最重要的是,它发生了后变量扩展和命令替换。
这意味着第二行
s="echo a; echo b"
echo $($s)
首先会$s
扩展为echo a; echo b
然后执行该命令没有将其拆分为由两个 s 组成的复合命令echo
。
(详细信息:$s
被分成四个单词,echo
,a;
,echo
和b
。封装$(...)
将其作为一个命令执行,echo
带有三个参数,a;
,echo
和b
,即,你本质上有echo $('echo' 'a;' 'echo' 'b')
)。
因此,给予最外面的echo
是字符串a; echo b
(实际上是三个$(...)
没有引号的单词),因为这是最里面的输出echo
。
比较一下
s1="echo a"
s2="echo b"
echo $($s1;$s2)
这会产生您所期望的结果。
是的,“eval
是邪恶的”,大多数时候,sh -c
如果你不知道自己在做什么,你可能会觉得笨拙并且脆弱。但是假设你有一些你信任的 shell 代码在一个字符串中在 shell 脚本中。在这种情况下,这些工具是使该代码正确执行的唯一(?)方法,因为这通常需要将字符串中的文本显式评估为 shell 代码(从开始到结束的所有评估阶段),特别是如果它是复合命令。
我认为这只是由于 Unix shell 与文本你很幸运能够echo $($s)
执行某件事根本不。
想想你必须采取哪些步骤才能让 C 程序执行一段以字符串形式给出的 C 代码......
答案2
经过一番阅读后,我尝试自己回答
为什么命令替换会有这样的行为?
$ a='echo x; echo y'
$ echo $($a) # expect 'x y'
命令替换
让我们注意到变量的替换$a
被执行在子外壳中在命令替换期间生成,而不是在当前 shell 中生成(1,2,3)。因此子shell执行命令$a
,而不是echo x; echo y
(变量的值a
被继承)
所以现在我们只需要找出为什么 shell 会这样:
$ a='echo x; echo y'
$ $a
x; echo y
分词
根据Bash 参考手册:
展开式有七种……展开式的顺序是:大括号展开;波形符扩展、参数和变量扩展、算术扩展和命令替换(以从左到右的方式完成);分词;和文件名扩展
——摘自本节3.5 外壳扩展
shell 扫描参数扩展、命令替换和算术扩展的结果这不会发生在用于分词的双引号内。
shell 对待每一个$IFS 的字符作为分隔符,并将其他扩展的结果拆分为单词这些作为字段终止符的字符
——摘自本节3.5.7 分词
(默认值为IFS
)<space><tab><newline>
IFS 变量仅用于拆分扩展结果,而不是所有单词(请参阅单词拆分)
元字符
一个字符,当不被引用时,分隔单词。元字符是空格、制表符、换行符或以下字符之一:
|
、&
、;
、(
、)
、<
或>
。
——摘自本节2. 定义
shell 读取和执行命令时的操作的简要描述。基本上,shell 执行以下操作:
- 读取其输入。
- 将输入分解为单词和运算符,遵守引用规则。这些标记由元字符分隔。
- 解析令牌分为简单命令和复合命令。
- 执行各种外壳扩展。
- 执行任何必要的重定向。
- 执行命令
——摘自本节3.1.1 外壳操作
现在我们看到实际上有两种不同的“分词”— 初始分词(步骤 2)和属于 shell 扩展的分词(步骤 4)。
初始分词(步骤 2)将;
, (
,)
等元字符视为分隔符;它的结果被解析(步骤 3),因此可以识别元字符和关键字。
随后 bash 执行各种 shell 扩展(步骤 4),其中包括“分词”——其中的一种。这种类型的分词仅将 的字符视为IFS
分隔符。它不关心元字符。其结果未解析再次,以及任何其他扩展的结果。因此,无法识别元字符和关键字。
这就是;
变量值内a
不被视为命令分隔符的原因。$a
变成'echo' 'x;' 'echo' 'y'
.即使 的值为,该单词a
也不会被视为元字符,因此命令将变为。'echo x ; echo y'
';'
$a
'echo' 'x' ';' 'echo' 'y'
概括:
元字符和关键字(if
、then
等while
)不能是扩展的结果,但程序名称、内置命令、函数和别名可以。它可能会带来一些混乱,因为它允许在变量中存储简单命令,而不允许使用复合命令执行相同的操作。
$ 'echo' 'a' ';' 'echo' 'b' # ';' is a literal
a ; echo b
$ cmd=echo
$ "$cmd" a ; $(printf echo) b # ';' is a metacharacter
a
b
$ cmd='echo a' # simple command is executed properly
$ $cmd
a
$ cmd='echo a;' # but metacharacters are not recognized
$ $cmd echo b
a; echo b
$ echo a $(echo ';') echo b # ';' is a literal
a ; echo b
$ $(printf if) true; then echo a; fi # parsing error
bash: syntax error near unexpected token 'then'
$ $(printf if) true; $(printf then) echo a; $(printf fi) # looking for command "if"
bash: if: command not found
bash: then: command not found
bash: fi: command not found
如何在不使用 eval 和 bash -c 的情况下对存储在变量中的命令列表执行命令替换(因为 eval 是邪恶的)?
也许,正确的答案是“这也很邪恶”,所以你不需要避免 eval :)
变量保存数据。函数保存代码。不要将代码放入变量中!..
——摘自文章我试图将命令放入变量中,但复杂的情况总是失败!