POSIX shell 中的命令替换后会删除引号吗?

does quote removal happen after command substitution in a POSIX shell?

POSIX shell 标准

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04

在第 2.6 节中说:

command substitution (...) shall be performed

(...)

Quote removal (...) shall always be performed last.

在我看来,在命令替换后没有执行引号删除:

$ echo "#"
#
$ echo '"'
"

符合预期,但是

$ echo $(echo '"')#"
>

我哪里不明白?

阅读后添加answer/comments:

按大家的说法,引号的考虑发生在解析的最开始,例如判断一个命令是否为偶数"acceptable"。那么为什么标准要强调,引用 删除 是在过程的后期执行的??

"then the outer command becomes echo "#" and is balanced"

那不是 'balanced' 因为第一个双引号 不算 。引号只有在命令行中不受阻碍地出现时才有意义。

为了验证,让我们看一下:

$ echo $(echo '"')#
"#

这是平衡的,因为 shell 确实认为 " 只是另一个字符。

相比之下,这是不平衡的,因为它只有一个 shell-active ":

$ echo $(echo '"')#"
> 

类似例子1

这里我们展示了同样的东西,但是使用了参数扩展而不是命令替换:

$ q='"'; echo $q
"

一旦 shell 将 " 替换为 $q,人们可能会认为存在不平衡的双引号。但是,那个双引号是 参数扩展 的结果,因此 不是 一个 shell-active 引号。

类似例子2

让我们考虑一个包含 file:

的目录
$ ls 
file
$ ls "file"
file

正如您在上面看到的,引号删除是在 ls 为 运行 之前执行的。

但是,考虑这个命令:

$ echo ls $(echo '"file"')
ls "file"

如您所见,ls $(echo '"file"') 扩展为 ls "file",这是上面 运行 成功执行的命令。现在,让我们尝试 运行 宁:

$ ls $(echo '"file"')
ls: cannot access '"file"': No such file or directory

如您所见,shell 不处理命令替换后保留的双引号。这是因为这些引号不被视为 shell-有效。因此,它们被视为普通字符并传递给 ls,它抱怨目录中不存在名称以 " 开头和结尾的文件。

同样的情况也发生在这里:

$ cmd='ls "file"'
$ $cmd
ls: cannot access '"file"': No such file or directory

POSIX标准

来自POSIX standard

Enclosing characters in single-quotes ( ' ' ) shall preserve the literal value of each character within the single-quotes

换句话说,一旦双引号出现在单引号内,它就没有什么特殊的功能了:它只是另一个字符。

该标准还提到转义和双引号作为保留字符 "the literal value" 的方法。

实际后果

刚接触 shell 的人通常希望将命令存储在变量中,如上面的 cmd='ls "file"' 示例所示。但是,因为引号和其他 shell-active 字符一旦存储在变量中就不再是 shell active,复杂的情况总是失败。这就引出了一篇经典文章:

"I'm trying to put a command in a variable, but the complex cases always fail!"