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

4

我正在编写一个shell,并且对POSIX shell规范感到有些困惑。比如,假设我有以下命令:

echo "`echo "a\\
b"`"

应该将Shell输出

ab

或者

a\
b

换句话说,在从命令替换文本中删除转义字符后,行继续符是否再次被删除? POSIX规范似乎指定不会再次发生删除行继续符的情况,但我测试过的所有shell(bash、dash和busybox的ash)都会再次执行行继续符的删除,导致测试脚本输出ab

脚本说明:

脚本中在命令替换内部的部分未经转义,产生以下结果:

echo "a\
b"

现在,如果再运行连续行删除操作,它将删除反斜杠-换行符对,生成命令 echo "ab" 在命令替换内部,否则反斜杠-换行符对仍然存在于 ab 之间。

1
你可以通过 bash --posix 命令以 POSIX 模式运行 Bash,并查看该命令的处理方式。 - Ian Kenney
1
@IanKenney 在 posix 模式下运行的 bash 与非 posix 模式下产生的结果完全相同。 - programmerjake
1
@IanKenney 好的。我提到我在几个不同的 shell 中进行了测试。其中一个我测试的 shell(dash)是专门设计用于实现仅符合 POSIX 规范的功能,不多也不少。 - programmerjake
2
查看 man bash(引用) 。很有趣... - Sylogista
1
在 POSIX sh 中,$( ) 是完全有效的。只有在 pre-POSIX Bourne 中它不可行 - 并且在 $() 中反斜杠处理要简单得多。也就是说,如果您只有关于“backticks”的问题,我建议修改标题来说明。 - Charles Duffy
显示剩余3条评论
1个回答

2
  • Old-style `...` command substitutions subject the embedded command to prior interpretation of \ as an escape character, and only then parse and execute it.

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

    • In other words: any embedded \$, \`, and \\ sequences are treated as escape sequences whose 2nd character should be treated literally.

    • Thus, \\<newline> in your command is reduced to \<newline>, because `...` interprets the \\ as an escaped, literal \

    • This interpretation happens before the embedded command is parsed and executed.

    • The \<newline> in the resulting command is therefore interpreted as a line continuation (inside the double-quoted string), which effectively removes the newline.

    • Therefore, the double-quoted string is effectively parsed as literal ab, and that is what is passed to the inner echo call.

    • In bash, you can verify this processing by setting debugging options: set -xv

  • Modern syntax $(...) avoids such surprises by providing a truly independent quoting context.

    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.

    • With $(...), the escaped line continuation in the embedded double-quoted string is retained (in bash, dash, ksh and zsh):

      echo "$(echo "a\\
      b")"
      
      # Output
      a\
      b         
      
    • Another reason to prefer $(...) is that it works the same in bash, dash, ksh and zsh, which is not true of `...`, whose behavior differs in ksh (see below).


主要类似于POSIX的shell的遵从性 - bashdashkshzsh 在`ksh`(版本为`93u+`)中,您的命令会出现问题,因为`ksh`要求在` `符号内部嵌入的`"`字符必须转义为`\"` - 这与标准不同。而`$(...)`语法则没有此要求。 而`bash`、`dash`和`zsh`会按规范处理基于`...`的命令(在`bash`中,无论是否以POSIX兼容模式运行)。请注意,这些shell也支持在`...`中作为双引号进行转义的`\"`,就像`ksh`所需求的那样。但可以说,支持此功能是偏离了标准,因为当在`...`上下文中的字符前面加上`\`时,`"`并不是组成转义序列的字符之一;例如,`echo "`echo \"a b\"`"`应该得到`"a b"`而不是`a b`。

可选阅读:跨 Shell 测试

如果您经常需要比较 POSIX 类似 Shell 的行为,请考虑使用 shall,这是一个 CLI 和 REPL 工具,可用于使用多个 POSIX 类似的 Shell 脚本或命令。

默认情况下,它针对安装的 bashdashkshzsh

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

shall ./tst

这将产生类似于以下的结果:

<code>shall</code> sample output

将以下文字翻译成中文:

请注意,使用ksh调用失败了,因为在`...`命令替换中,ksh需要将"进行转义为\"
同样,使用$(...)将会避免这个问题。

npm registry(Linux和macOS)安装shall

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

如果已经安装了Node.js,请按照以下步骤进行安装:

[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)。

1
我错过了未转义的内容再次经过所有解析步骤的过程,分割成行后的第一步是删除行继续符。 - programmerjake
@programmerjake:明白了——我甚至没有质疑过那个方面;嵌入式命令再次解析显然是实际工作的方式,但从规范上来看对我来说并不明显。 - mklement0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接