Bourne shell 兼容的“${#string}”中字符串的“长度”是多少?

Mar*_*ler 3 bash zsh unicode bourne-shell byte

从这次讨论中得出:

\n

当我有(zsh 5.8,bash 5.1.0)

\n
var="ASCII"\necho "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"\n
Run Code Online (Sandbox Code Playgroud)\n

答案很简单:这是5个字符,占用5个字节。

\n

现在,var=M\xc3\xbcller产量

\n
M\xc3\xbcller has the length 6, and is 7 bytes long\n
Run Code Online (Sandbox Code Playgroud)\n

这表明该${#}运算符计算的是代码点,而不是字节。这在 POSIX 中有点不清楚,他们说它计算“字符”。char通常,如果 POSIX C 中的字符不是八位字节,那么这会更清楚。

\n

无论如何:不错!还好,看到了LANG==en_US.utf8

\n

现在,

\n
M\xc3\xbcller has the length 6, and is 7 bytes long\n
Run Code Online (Sandbox Code Playgroud)\n
\xe2\x80\x8d\xe2\x99\x80\xef\xb8\x8f has the length 5, and is 17 bytes long\n
Run Code Online (Sandbox Code Playgroud)\n

Soooo,我们将“深色肤色的美人鱼”分解为 Unicode 代码点

\n
    \n
  1. 人鱼
  2. \n
  3. 深色肤色
  4. \n
  5. 零宽度连接器
  6. \n
  7. 女性
  8. \n
  9. 打印 将前一个字符打印为表情符号
  10. \n
\n

好吧,我们实际上是在计算 Unicode 代码点!

\n
var=\'\xe2\x80\x8d\xe2\x99\x80\xef\xb8\x8f\'\necho "${var} has the length ${#var}, and is $(printf "%s" "$var"| wc -c) bytes long"\n
Run Code Online (Sandbox Code Playgroud)\n
e\xcc\x81 has the length 9, and is 9 bytes long\n
Run Code Online (Sandbox Code Playgroud)\n

(当然,我的控制台字体决定将\xc2\xb4与后面的空格结合,而不是前面的e。后者是正确的。但是让我们把对此的愤怒留到其他时候吧。)

\n

嗯,这里有一个轻微的“wat”。

\n
\xe2\x80\x8d\xe2\x99\x80\xef\xb8\x8f has the length 5, and is 17 bytes long\n
Run Code Online (Sandbox Code Playgroud)\n

这就是我放弃的地方。

\n

echo $varecho ${var}并且echo "${var}"所有“正确”发出三个字节。然而,echo ${#var}告诉我它有 9 个字符。

\n

这是在哪里记录/标准化的,这一切的规则是什么?

\n

Sté*_*las 5

在 POSIX 兼容 shell(不是 Bourne shell,该功能来自 Korn shell)中,${#var}like会计算\xc2\xb9 中的字符wc -m数,并且如果存储在中的字节序列无法解码为当前字符中的字符,则行为未指定语言环境。$var$var

\n

根据当前区域设置(其LC_CTYPE类别)将字节解码为字符。在使用 UTF-8 作为字符编码的语言环境中,0xc3 0xa9 序列将被解码为字符,而在使用 ISO8859-1 的语言环境中,该序列\xc3\xa9将被解码为.\xc3\x83\xc2\xa9\xe7\x9f\x87

\n

无论如何,它与 Unicode 代码点关系不大。它也不同于计算终端或任何其他显示设备显示时的字素簇数量或字符串宽度。

\n

在:

\n
var="e\\xcc\\x81"\n
Run Code Online (Sandbox Code Playgroud)\n

$var包含 9 个字节和 9 个字符:e\\xcc\\x81

\n

有些printf(在格式参数或%b格式指令的参数中)和echo实现将扩展\\xcc到 0xcc 字节,但并非全部都会。根据 POSIX,\\x在对这些的争论中会导致未指定的行为。(确实在格式参数和/中\\351扩展到 0xe9 字节)。printf\\0351echo%b

\n

如果你想在//中$var包含0x65, 0xcc,0x81字节(以及现在越来越多的shell),你可以这样做:ksh93zshbash

\n
var=$\'e\\xcc\\x81\'\n
Run Code Online (Sandbox Code Playgroud)\n

或者你总是可以这样做:

\n
var=$(printf \'e\\314\\201\')\n
Run Code Online (Sandbox Code Playgroud)\n

locale charmap然后,在输出的语言环境中UTF-8$var将包含 3 个字节(如 所示)、2 个字符(如或wc -c所示)、1 个字素簇(如 GNU 所示),通常以宽度 1 显示(如 GNU 所示)。wc -m${#var}grep -Po \'\\X\'wc -L

\n

如果调用 shell 时以及解析和执行代码时的语言环境将 UTF-8 作为字符集,则在多个 shell 中,您还可以执行以下操作:

\n
var=$\'e\\u0301\'\n
Run Code Online (Sandbox Code Playgroud)\n

用于包含和 U+0301(组合锐音符号)字符$var的 UTF-8 编码。e

\n

如果语言环境的字符集不是 UTF-8,则不同 shell 的行为会有所不同。此外,将 Unicode 代码点扩展为字符时考虑的区域设置是在解析代码时还是在执行代码时有效,这取决于 shell。如果该字符不存在于区域设置的字符映射中,您还会发现行为的变化。

\n

在 Bourne shell 中,要获取字符串的字符长度,您必须求助于其他实用程序,例如:

\n
length=`expr "x$var" : \'.*\' - 1` || :\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
length=`printf %s "$var" | wc -m`\n
Run Code Online (Sandbox Code Playgroud)\n

不过,如果您发现一个足够旧的系统仍然具有 Bourne shell,则很可能它wc不支持-m或者不会有printf命令。

\n
\n

\xc2\xb9 POSIX 本身不指定字节序列和字符序列之间的映射,即使在 POSIX 语言环境中也不指定,只有一些 API 来定义和检索该映射或将字节序列转换为字符序列 ( ) wchar_t。系统通常使用字符集标准字符集,例如 UTF-8,这是另一个 ISO 标准(ISO/IEC 10646 又名 Unicode)定义的字符集转换格式。某些系统(例如 GNU 系统)实际上使用 Unicode 代码点作为值,wchar_t而不管区域设置如何。

\n