bel*_*kka 4 command-line bash command-substitution variable-substitution
$ echo $(echo x; echo y)
x y
$ a='echo x; echo y'
$ echo $($a) # expect 'x y'
x; echo y
Run Code Online (Sandbox Code Playgroud)
eval
和的情况下对存储在变量中的命令列表执行命令替换bash -c
?echo $(eval $a)
并且echo $(bash -c "$a")
实际上做了我想要的,但我听说使用 eval 经常是解决问题的错误方法,所以我想知道如何在没有这些命令的情况下进行管理(使用bash -c
实际上是一样的)
分词发生在命令评估的后期。对您来说最重要的是,它发生在变量扩展和命令替换之后。
这意味着第二行
s="echo a; echo b"
echo $($s)
Run Code Online (Sandbox Code Playgroud)
将首先$s
扩展为echo a; echo b
然后执行该命令而不将其拆分为由两个echo
s组成的复合命令。
(详见:$s
获取拆分成四个词,echo
,a;
,echo
和b
的封装。$(...)
执行该作为一个命令,echo
有三个参数,a;
,echo
和b
。)
echo
因此,给予最外层的是字符串a; echo b
(实际上是三个$(...)
没有引号的单词),因为这是最内层echo
.
比较一下
s1="echo a"
s2="echo b"
echo $($s1;$s2)
Run Code Online (Sandbox Code Playgroud)
这会导致您期望的结果。
是的,eval
大多数时候“是邪恶的”,而且sh -c
笨重而脆弱。但是,如果您有一些信任shell 脚本中字符串的 shell 代码,那么这些工具是使该代码正确执行的唯一 (?) 方法,因为这通常需要显式评估字符串中的文本作为 shell 代码(包括从开始到结束的所有评估阶段)。特别是如果它是一个复合命令。
我认为这只是由于Unix外壳的亲密关系,以文字,你是幸运的,足有echo $($s)
执行的东西可言。
想想你必须采取哪些步骤来让 C 程序执行一段 C 代码,它以字符串形式给出......
经过一些阅读后,我尝试自己回答
$ a='echo x; echo y'
$ echo $($a) # expect 'x y'
Run Code Online (Sandbox Code Playgroud)
让我们注意变量的替换$a
是在命令替换期间生成的子 shell中执行的,而不是在当前 shell ( 1 , 2 , 3 ) 中执行。因此子shell执行命令$a
,而不是echo x; echo y
(变量的值a
是继承的)
所以现在我们只需要找出为什么 shell 的行为是这样的:
$ a='echo x; echo y'
$ $a
x; echo y
Run Code Online (Sandbox Code Playgroud)
根据Bash 参考手册:
展开有七种……展开的顺序是:大括号展开;波浪号扩展、参数和变量扩展、算术扩展和命令替换(以从左到右的方式完成);分词;和文件名扩展
shell 扫描没有出现在双引号内的参数扩展、命令替换和算术扩展的结果以进行分词。
shell 将$IFS 的每个字符视为分隔符,并使用这些字符作为字段终止符将其他扩展的结果拆分为单词
— 来自3.5.7 分词节
(的默认值IFS
是<space><tab><newline>
)
IFS 变量只用于拆分展开的结果,而不是所有单词(参见 Word Splitting)
元字符
一个在不加引号时分隔单词的字符。元字符是空格,制表,换行,或以下字符中的一个:
|
,&
,;
,(
,)
,<
,或>
。
shell 在读取和执行命令时的操作的简要说明。基本上,shell 执行以下操作:
- 读取其输入。
- 将输入分解为单词和运算符,遵守引用规则。这些标记由元字符分隔。
- 将标记解析为简单和复合命令。
- 执行各种外壳扩展。
- 执行任何必要的重定向。
- 执行命令
— 来自3.1.1 Shell 操作部分
现在我们看到实际上有两种不同的“分词” ——初始分词(第 2 步)和分词,这是一种壳扩展(第 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
Run Code Online (Sandbox Code Playgroud)
可能正确的答案是“这也很邪恶”,因此您无需避免使用 eval :)
变量保存数据。函数保存代码。不要将代码放在变量中!...
— 从文章中我试图将命令放入变量中,但复杂的情况总是失败!