排序 LC_ALL=C vs LC_ALL=C.utf8

Bin*_*min 8 sort unicode locale

linux sort 命令是否区分 C 和 C.utf-8 语言环境?

排序手册说使用 LC_ALL=C 按字节值排序,但我看到 C.utf-8 也允许 utf8 值(而不仅仅是 ASCII) - 但排序手册根本没有提到这个语言环境选项。

在运行 LC_ALL=C sort file.txt 和 LC_ALL=C.utf8 sort file.txt 时,无论文件是否包含 utf 8 个字符,我都看不到两者之间的任何区别,两者似乎都有效。

那么有什么已知的区别吗?

Sté*_*las 11

LC_ALL=C sort按字节值排序。它将按字节值对以任何字符集写入的任何输入进行排序,而不仅仅是 ASCII¹。

UTF-8 编码具有按字节值排序与按 Unicode 码位排序相同的特性(memcmp()会发现 U+1234 的编码大于 U+1233 的编码或任何小于 0x1234 的 Unicode 码位)。

C.utf-8C.utf8或者C.UTF-8(后者在我的经验中更常见)不是 POSIX 标准化的语言环境,但是无论在哪里找到它们,它们都是具有 C 语言环境的大部分属性的语言环境,除了字符集是 UTF- 8.

LC_ALL=C.UTF-8 sort将根据代码点对输入进行排序,但最终可能会在比较之前解码 UTF-8 或调用strcoll()/strxfrm()重型机器,这最终会浪费精力,因为对于 UTF-8,使用memcmp()就足够了。

在许多使用 Linux 作为内核的非嵌入式操作系统上都可以找到GNUsort和 GNU libc(这里还在输入中添加了 GNUsort支持的NUL 字符,即使strcoll()不支持):

$ printf 'a\0£1\na\0€2\n' | LC_ALL=C ltrace -e strcoll -e memcmp sort
sort->memcmp("a\0\302\2431", "a\0\342\202\254", 5)                      = -1
a£1
a€2
$ printf 'a\0£1\na\0€2\n' | LC_ALL=C.UTF-8 ltrace -e strcoll -e memcmp sort
sort->strcoll("a", "a")                                                 = 0
sort->strcoll("\302\2431", "\342\202\2542")                             = -31
a£1
a€2
Run Code Online (Sandbox Code Playgroud)

(实际上,你会发现,如果两个字符串比较具有相同的字节数,GNUsort调用memcmp()首先调用之前strcoll()的情况下,它们是相同的,因为memcmp()是这样比较便宜strcoll())。

该输出的某些计时重复了 1,000,000 次:

$ printf 'a\0£1\na\0€2\n%.0s' {1..1000000} > file.test
$ wc -mc file.test
10000000 13000000 file.test
$ time LC_ALL=C sort file.test > /dev/null
LC_ALL=C sort file.test > /dev/null  0.74s user 0.06s system 390% cpu 0.205 total
$ time LC_ALL=C.UTF-8 sort file.test > /dev/null
LC_ALL=C.UTF-8 sort file.test > /dev/null  6.04s user 0.12s system 522% cpu 1.179 total
Run Code Online (Sandbox Code Playgroud)

因此,要按代码点对 UTF-8 编码文本进行排序,使用CC.UTF-8不会在功能上有所不同,但使用C可能更有效,具体取决于sort实现。

现在,并非所有字节序列都形成有效的 UTF-8,因此当涉及非 UTF-8 输入时,即包含无法解码为 UTF-8 的字节序列的输入,您可能会发现行为不同C和之间C.UTF-8。仍然在 GNU 系统上:

$ print -l 'a\200b' 'a\201b' | LC_ALL=C sort -u
a?b
a?b
$ print -l 'a\200b' 'a\201b' | LC_ALL=C.UTF-8 sort -u
a?b
Run Code Online (Sandbox Code Playgroud)

(哪里?是我的终端模拟器对未知事物的再现)

在 C.UTF-8 中,strcoll()对不构成有效 UTF-8 文本的那两个字符串返回 0,实际上报告它们具有相同的排序顺序。

在 C 语言环境中,任何由 0 以外的LINE_MAX字节序列组成且不长于字节的行都是有效文本。在 C.UTF-8 中,还有进一步的限制。这a\200b在 UTF-8 中无效,因此它不是文本,因此根据 POSIX,它的行为sort未指定。

附带说明:在 GNU 系统上,虽然消息的语言LC_ALL=C优先$LANGUAGELC_ALL=C.UTF-8但不是。

$ LC_ALL=C LANGUAGE=fr:es:en sort /
sort: read failed: /: Is a directory
$ LC_ALL=C.UTF-8 LANGUAGE=fr:es:en sort /
sort: échec de lecture: /: est un dossier
Run Code Online (Sandbox Code Playgroud)

¹ 还请注意,C语言环境字符集不必基于 ASCII,并且 ASCII 仅涵盖 0 到 127 的值C。使用 ASCII 的语言环境仍然将字节 128 到 255 视为字符,尽管是未定义的字符。该C区域设置字符集必须保证每个字符,虽然一个字节,所以C语言环境的字符集不能是UTF-8