/bin/sh 计算字符串,而 Bash 不计算

/bin/sh 计算字符串,而 Bash 不计算

运行下面的脚本时,我得到两个不同的输出,具体取决于所使用的 shell 是否为shbash

regex(){
     echo 's/\(.* \)\(!\{0,1\}\)has(/\1\2MOCK_has(/g'
}

replace_builtins(){
    sed -e "$(regex)"
}

echo 'if !has(\"nvim\"): ' | replace_builtins
  • 巴什:if !MOCK_has(\"nvim\"):
  • :??MOCK_has(\"nvim\"):

(这些问号最初是从终端逐字复制的,但在我保存帖子时消失了。它本质上是不可打印的字符)

我想知道在 POSIX sh 模式下运行时发生了什么来解释这种现象。

编辑:对于奖励积分,请解释为什么在函数中echo替换时也会在 Bash 中发生这种情况:printfregex

     printf 's/\(.* \)\(!\{0,1\}\)has(/\1\2MOCK_has(/g'

答案1

解释是在POSIX 规范echo:

要写入标准输出的字符串。如果第一个操作数是-n,或者任何操作数包含 <反斜杠> 字符,则结果是实现定义的。

POSIX 主要是对历史实践进行编纂,有时历史实践并不一致。某些 shell 将参数中的转义序列扩展为echo,例如\t扩展为制表符并\1扩展为字节值为 1 ( ) 的字符^A。其他 shell 将反斜杠视为普通字符。

打印任意字符串的可移植方法是使用printf.printf始终在其第一个参数(格式)中扩展反斜杠转义序列。要按字面意思打印字符串,请使用

printf %s 's/\(.* \)\(!\{0,1\}\)has(/\1\2MOCK_has(/g'

要按字面意思打印字符串并在末尾添加换行符,请使用

printf '%s\n' 's/\(.* \)\(!\{0,1\}\)has(/\1\2MOCK_has(/g'

请注意,如果在 shell 脚本中使用单引号文字写入字符串,则单引号字符需要写为'\'''。这是关于 shell 语法的问题,与字面打印字符串完全不同的问题。

答案2

您发现了一个有趣的问题...

问题是缺少 POSIX 兼容性dash

POSIX 区分小型嵌入式系统和声称 UNIX 兼容性的大型系统(例如 Linux)的基本 POSIX 兼容性级别。在后一种情况下,系统需要实现所有所谓的 XSI 扩展。

XSI 兼容系统需要扩展echo参数中的某些反斜杠转义。

bashbash可以编译为符合 POSIX/XSI 的行为(例如,这是在 Solaris 和 MacOS 上完成的),但Linux 上的二进制文件并未这样做。如果bash针对 POSIX/XSI 合规性进行编译,则它会正确处理echo参数的反斜杠转义,并且您的示例代码将适用于bashSolaris 或 MacOS 中的此类二进制文件,因为示例代码中没有 POSIX/XSI 转义序列。

由于bashLinux 上不兼容 XSI,因此它根本不会扩展参数的反冲转义,这就是您的示例代码也echo适用于 Linux 的原因。bash

dash另一方声称符合 POSIX/XSI 并扩展了echo参数的反斜杠转义。如果正确实现了 POSIX/XSI 合规性,您的示例代码也dash可以正常工作。dash这是因为您的示例代码不包含任何 POSIX/XSI 反斜杠转义序列。

POSIX/XSI需要echo扩展:

\0nnn  for an octal number that represents the related character

您的示例代码包含反斜杠序列:

\1 for the first sed subexpression

\2 for the second sed subexpression

并且这不是 POSIX/XSI 转义序列的一部分,因此不允许来自 POSIX 兼容 shell 的echo内置函数来扩展它们。然而,由于八进制数的扩展不正确,因此 POSIX 禁止这样做。这就是您的示例代码失败并显示 的原因。echodash\1\2dash

我建议您提交错误报告dash并等待修复,或者替换echo argprintf '%s\n' arg.这甚至可以工作,因为内置dash的已知错误不会影响您的情况。printfdash

因此我们可以列出 POSIX/XSI 错误dash

  • 不支持多字节字符。

  • 即使这是被禁止的,也会\nnn在参数中扩展echo

  • 即使这是必需的,也不会扩展\nnn参数。printf

相关内容