关于sh:POSIX shell:在反引号命令替换中转义行继续

POSIX shell: escaping line-continuations in backquote command-substitutions

我正在写一个shell,我对POSIX shell规范有些困惑。说我有命令:

1
2
echo"`echo"a\\\\
b"`"

shell输出是否应该

1
ab

1
2
a\\
b

换句话说,在命令替换中从文本中删除转义符后,是否再次删除了行继续符? POSIX规范似乎指定不再进行行连续删除,但是,我测试的所有shell(bash,破折号和busybox的灰烬)都再次运行了行连续删除,导致测试脚本输出

脚本说明:

命令替换内的脚本部分未转义,产生:

1
2
echo"a\\
b"

现在,如果再次运行换行删除,它将删除反斜杠-换行符对,并在命令替换内生成命令echo"ab",否则反斜杠-换行符对仍将位于ab


  • 旧式`...`命令替换使嵌入式命令先将\\解释为转义字符,然后才解析并执行它。

    • POSIX Shell规范的这一段很关键(为便于阅读而进行了编辑):

    Within the backquoted style of command substitution, \\ shall retain its literal meaning, except when followed by: $, `, or \\.

    • 换句话说:任何嵌入的\\$\\`\\\\序列都被视为转义序列,应按字面意义对待其第二个字符。

    • 因此,命令中的\\\\<newline>简化为\\<newline>,因为`...`\\\\解释为转义的文字\\

    • 此解释发生在解析和执行嵌入式命令之前。

    • 因此,结果命令中的\\<newline>被解释为行继续(在双引号引起来的字符串内),这有效地删除了换行符。

    • 因此,将双引号字符串有效地解析为文字ab,这就是传递给内部echo调用的内容。

    • bash中,可以通过设置调试选项来验证此处理:set -xv

  • 现代语法$(...)通过提供真正独立的引用上下文来避免此类意外。

    • 引用规范的"shell和实用程序规定"部分(添加了重点):

    Because of these inconsistent behaviors, the backquoted variety of command substitution is not recommended for new applications that nest command substitutions or attempt to embed complex scripts.

    • 使用$(...),将保留嵌入的双引号字符串中的转义行继续(在bashdashkshzsh中):

      1
      2
      3
      4
      5
      6
      echo"$(echo"a\\\\
      b")"

      # Output
      a\\
      b
    • 首选$(...)的另一个原因是,它在bashdashkshzsh中的工作原理相同,而对于`...`却不成立,而`...`的行为在ksh中有所不同(参见下文)。

在类似POSIX的主要shell中的合规性-bashdashkshzsh

  • ksh(已通过版本93u+验证)中,您的命令会中断,因为ksh需要嵌入的"字符。 `...`中的内部要转义为"-这是与标准的偏差。
    语法$(...)没有此要求。

  • bashdashzsh根据规范要求处理基于`...`的命令(对于bash,无论它是否以POSIX兼容模式运行)。

    • 请注意,根据ksh的要求,这些shell程序还支持"-在`...`中作为双引号转义。
    • 可以说,支持此做法是对标准的偏离,因为在`...`上下文中,当"不在以\\开头的转义序列中。例如,echo"`echo "a b"`"应该生成"a b",而不是a b

可选阅读:跨shell测试

如果您发现自己需要经常比较类似POSIX的shell的行为,请考虑使用shall,我的CLI和REPL来调用具有多个类似POSIX的shell的shell脚本或命令。

默认情况下,它的目标是bashdashkshzsh(无论已安装的是哪个)。

例如,如果将命令放在脚本./tst中,则将按以下方式调用shall

1
shall ./tst

其结果类似于:

<x53>

请注意如何用ksh调用失败,因为ksh要求`...`命令替换内的"要转义为"
同样,使用$(...)将绕过此问题。

从npm注册表中安装shall(Linux和macOS)

注意:即使不使用Node.js,它的包管理器npm也可以跨平台工作并且易于安装;尝试
curl -L https://git.io/n-install | bash

安装了Node.js之后,按以下步骤安装:

1
[sudo] npm install shall -g

注意:

  • 是否需要sudo取决于安装Node.js的方式以及以后是否更改了权限。如果出现EACCES错误,请使用sudo重试。
  • -g确保全局安装,需要将shall放入系统的$PATH中。

手动安装(任何带有bash的Unix平台)

  • 将此bash脚本下载为shall
  • 使用chmod +x shall使它可执行。
  • 将其移动或符号链接到$PATH中的文件夹,例如/usr/local/bin(macOS)或/usr/bin(Linux)。