运行下面的脚本时,我得到两个不同的输出,具体取决于所使用的 shell 是否为sh
或bash
:
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 中发生这种情况:printf
regex
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
参数中的某些反斜杠转义。
bash
bash
可以编译为符合 POSIX/XSI 的行为(例如,这是在 Solaris 和 MacOS 上完成的),但Linux 上的二进制文件并未这样做。如果bash
针对 POSIX/XSI 合规性进行编译,则它会正确处理echo
参数的反斜杠转义,并且您的示例代码将适用于bash
Solaris 或 MacOS 中的此类二进制文件,因为示例代码中没有 POSIX/XSI 转义序列。
由于bash
Linux 上不兼容 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 禁止这样做。这就是您的示例代码失败并显示 的原因。echo
dash
\1
\2
dash
我建议您提交错误报告dash
并等待修复,或者替换echo arg
为printf '%s\n' arg
.这甚至可以工作,因为内置dash
的已知错误不会影响您的情况。printf
dash
因此我们可以列出 POSIX/XSI 错误dash
:
不支持多字节字符。
即使这是被禁止的,也会
\nnn
在参数中扩展echo
即使这是必需的,也不会扩展
\nnn
参数。printf