bash 命令替换与变量中字符串命令的行为

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)
  1. 为什么命令替换会以这种方式运行?
  2. 如何在不使用eval和的情况下对存储在变量中的命令列表执行命令替换bash -c

echo $(eval $a)并且echo $(bash -c "$a")实际上做了我想要的,但我听说使用 eval 经常是解决问题的错误方法,所以我想知道如何在没有这些命令的情况下进行管理(使用bash -c实际上是一样的)

Kus*_*nda 6

分词发生在命令评估的后期。对您来说最重要的是,它发生变量扩展和命令替换之后。

这意味着第二行

s="echo a; echo b"
echo $($s)
Run Code Online (Sandbox Code Playgroud)

将首先$s扩展为echo a; echo b然后执行该命令而不将其拆分为由两个echos组成的复合命令。

(详见:$s 获取拆分成四个词,echoa;echob的封装。$(...)执行该作为一个命令,echo有三个参数,a;echob。)

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 代码,它以字符串形式给出......

  • 更确切地说,`$s`首先被拆分为`echo`、`a;`、`echo`和`b`(假设默认值为`$IFS`),第一个单词用于派生命令执行(`echo`,在这种情况下是一个内置函数)并且所有 4 个参数都传递给 `echo`。`echo` 输出倒数第二个,中间有空格字符,最后是换行符。`$()` 去掉换行符,然后进行第二轮 split+glob。这导致 `a;`、`echo` 和 `b` 也被传递给外部的 `echo`(再次假设默认值为 `$IFS`)。 (3认同)

bel*_*kka 5

经过一些阅读后,我尝试自己回答

为什么命令替换会以这种方式运行?

$ 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 参考手册

展开有七种……展开的顺序是:大括号展开;波浪号扩展、参数和变量扩展、算术扩展和命令替换(以从左到右的方式完成);分词;和文件名扩展

— 来自第3.5壳扩展


shell 扫描没有出现在双引号内的参数扩展、命令替换和算术扩展结果以进行分词。

shell 将$IFS 的每个字符视为分隔符,并使用这些字符作为字段终止符将其他扩展的结果拆分为单词

— 来自3.5.7 分词

(的默认值IFS<space><tab><newline>


IFS 变量只用于拆分展开的结果,而不是所有单词(参见 Word Splitting)

— 来自附录 B 与 Bourne Shell 的主要区别


元字符

一个在不加引号时分隔单词的字符。元字符是空格,制表,换行,或以下字符中的一个:|&;()<,或>

— 来自第2。定义


shell 在读取和执行命令时的操作的简要说明。基本上,shell 执行以下操作:

  1. 读取其输入。
  2. 将输入分解为单词和运算符,遵守引用规则。这些标记由元字符分隔。
  3. 将标记解析为简单和复合命令。
  4. 执行各种外壳扩展
  5. 执行任何必要的重定向。
  6. 执行命令

— 来自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'.

概括:

元字符和关键字(ifthenwhile等)不能扩张的结果,但节目名称,内置命令,函数和别名做即可。它可能会引起一些混乱,因为它允许将简单的命令存储在变量中,而不允许对复合命令执行相同的操作。

$ '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 和 bash -c 的情况下对存储在变量中的命令列表执行命令替换(因为 eval 是邪恶的)?

可能正确的答案是“这也很邪恶”,因此您无需避免使用 eval :)

变量保存数据。函数保存代码。不要将代码放在变量中!...

— 从文章中我试图将命令放入变量中,但复杂的情况总是失败!

链接

Unix 堆栈交换:

Bash shell 命令替换

为什么变量在子shell中可见?

括号真的把命令放在子shell中吗?

其他:

Bash 参考手册

我试图将命令放入变量中,但复杂的情况总是失败!

debian 错误跟踪系统中的对话