在如何将 HEREDOC 文本放入 shell 脚本变量中?有人使用 a 报告问题这里的文档里面有一个带引号的分隔符$(...)
命令替换\
,文档内行尾的反斜杠会触发换行符-连接行延续,而此处的文档相同外部命令替换按预期工作。
这是一个简化的示例文档:
cat <<'EOT'
abc ` def
ghi \
jkl
EOT
这包括行尾的一个反引号和一个反斜杠。分隔符被引用,因此体内不会发生扩展。在所有 Bourne 类似的程序中,我可以发现它逐字输出内容。如果我将相同的文档放入命令替换中,如下所示:
x=$(cat <<'EOT'
abc ` def
ghi \
jkl
EOT
)
echo "$x"
那么它们的行为不再相同:
dash
、ash
、zsh
、ksh93
、 BusyBoxash
、mksh
和 SunOS 5.10 POSIXsh
都像以前一样给出文档的逐字内容。- Bash 3.2 因不匹配的反引号而给出语法错误。通过匹配的反引号,它尝试将内容作为命令运行。
- Bash 4.3 将“ghi”和“jkl”折叠到一行,但没有错误。这
--posix
选项不影响这个。善行难陀告诉我(谢谢!)其pdksh
行为方式相同。
在最初的问题中,我说这是 Bash 解析器中的一个错误。是吗? [更新:是的] 我能找到的来自 POSIX 的相关文本(全部来自 Shell 命令语言定义)是:
- §2.6.3 命令替换:
对于 $(command) 形式,左括号后面到匹配右括号的所有字符构成命令。任何有效的 shell 脚本都可以用于命令,但仅由重定向组成的脚本除外,该脚本会产生未指定的结果。
- §2.7.4 此处文档:
如果任何部分单词被引用时,分隔符应通过执行引号去除来形成单词,并且此处文档行不应扩展。
- §2.2.1 转义字符(反斜杠):
如果 <newline> 跟在 <backslash> 后面,shell 会将其解释为行继续。在将输入拆分为标记之前,应删除 <backslash> 和 <newline>。
- §2.3 令牌识别:
当io_here标记已被语法识别(参见外壳语法),紧接着下一行的一行或多行新队令牌构成一个或多个此处文档的主体,并应根据以下规则进行解析此处文档。
当它不处理一个io_here,shell 应通过将下面第一个适用的规则应用于其输入中的下一个字符来将其输入分解为标记。 ...
...
- 如果当前字符是<反斜杠>、单引号或双引号并且未加引号,则它将影响后续字符的引用,直到被引用文本的末尾。引用规则如下所述引用。在令牌识别期间,不应实际执行任何替换,并且结果令牌应准确包含输入中出现的字符(<newline> 连接除外),未经修改,包括在 和 end 之间的任何嵌入或封闭的引号或替换运算符的引用文本。
我对此的解释是,$(
直到终止之前的所有字符)
都逐字组成 shell 脚本;出现此处文档,因此此处文档处理发生而不是普通的标记化;然后,此处的文档有一个带引号的分隔符,这意味着其内容将被逐字处理;并且转义字符永远不会出现在其中。然而,我可以看到一个论点,即这种情况根本没有得到解决,并且这两种行为都是允许的。我也可能在某个地方跳过了一些相关文本。
- 这种情况在其他地方是否已经说得更清楚了?
- 可移植脚本应该能够依赖什么(理论上)?
- 标准是否强制要求对这些 shell(Bash 3.2/Bash 4.3/其他所有 shell)进行特定处理?禁止?允许吗?
答案1
这是在 Bash 的邮件列表上询问的,并且维护者确认这是一个错误
他们还提到 POSIX 中的文本“不一定含糊不清,但确实需要仔细阅读。”,所以我要求对此进行澄清。他们的回答包括问题的描述和标准的解释如下:
命令替换是一个转移注意力的事情;它的相关性仅在于它指出了错误所在。
这里文档的分隔符被引用,因此这些行不会扩展。在这种情况下,shell 从输入中读取行,就像它们被引用一样。如果反斜杠出现在引用它的上下文中,它不会充当转义字符(见下文),并且不会发生反斜杠换行符的特殊处理。事实上,如果分隔符的任何部分被引用,则此处文档行将被读取为单引号。
Posix 2.2.1 中的文本写得很笨拙,但这意味着反斜杠仅在不加引号时才会被特殊处理。您可以引用反斜杠并仅使用单引号或另一个反斜杠来禁止所有扩展。
仔细阅读部分是暗示单引号的“未扩展”文本。标准在 2.2 中说,这里的文档是“另一种引用形式”,但单词根本不扩展的唯一引用形式是单引号。因此,它是一种与单引号完全相同的引用形式,但又不同于单引号。