Linux的sort如何对字符串进行排序

介绍

这一切都始于一个简短的脚本,该脚本应该结合地址信息 e-mail 员工从邮件列表用户列表中获取,员工职位从HR部门数据库中获取。 两个列表均导出为 Unicode 文本文件 UTF-8 并以 Unix 行结尾保存。

内容 邮件.txt

Иванов Андрей;[email protected]

内容 buhg.txt

Иванова Алла;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Абаканов Михаил;маляр

为了合并,文件通过 Unix 命令进行排序 分类 并提交给Unix程序的输入 加入,意外失败并出现错误:

$> sort buhg.txt > buhg.srt
$> sort mail.txt > mail.srt
$> join buhg.srt mail.srt > result
join: buhg.srt:4: is not sorted: Иванов Андрей;слесарь

肉眼观察排序结果,总体来说排序是正确的,但在男女姓氏重合的情况下,女性排在男性之前:

$> sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванова Алла;маляр
Иванов Андрей;слесарь

看起来像是 Unicode 中的排序故障,或者像是排序算法中女权主义的体现。 当然,第一种说法更合理。

我们暂时把它推迟吧 加入 并专注于 分类。 让我们尝试用科学的戳戳来解决这个问题。 首先,让我们更改区域设置 EN_USru_RU。 要排序,设置环境变量就足够了 LC_COLLATE,但我们不会在琐事上浪费时间:

$> LANG=ru_RU.UTF-8 sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванова Алла;маляр
Иванов Андрей;слесарь

没有任何改变。

让我们尝试将文件重新编码为单字节编码:

$> iconv -f UTF-8 -t KOI8-R buhg.txt 
 | LANG=ru_RU.KOI8-R sort 
 | iconv -f KOI8-R -t UTF8

一切都没有改变。

你无能为力,只能在互联网上寻找解决方案。 没有任何直接与俄罗斯姓氏相关的内容,但存在关于其他排序奇怪现象的问题。 例如,这里有一个问题: unix 排序将“-”(破折号)字符视为不可见。 简而言之,字符串“ab”、“aa”、“ac”被排序为“aa”、“ab”、“ac”。

答案在任何地方都是标准的:使用程序员区域设置 “C” 你会很高兴。 咱们试试吧:

$> LANG=C sort buhg.txt
Ёлкина Элла;крановщица
Абаканов Михаил;маляр
Иванов Андрей;слесарь
Иванова Алла;адвокат

有些事情发生了变化。 伊万诺夫一家按照正确的顺序排列,尽管约基纳在某个地方滑倒了。 让我们回到最初的问题:

$> LANG=C sort buhg.txt > buhg.srt
$> LANG=C sort mail.txt > mail.srt
$> LANG=C join buhg.srt mail.srt > result

正如互联网所承诺的那样,它没有错误地运行。 尽管约基娜在第一线,但情况仍然如此。

问题似乎解决了,但为了以防万一,让我们尝试另一种俄语编码 - Windows CP1251:

$> iconv -f UTF-8 -t CP1251 buhg.txt 
 | LANG=ru_RU.CP1251 sort 
 | iconv -f CP1251 -t UTF8 

奇怪的是,排序结果将与区域设置一致 “C”,因此整个示例运行没有错误。 某种神秘主义。

我不喜欢编程中的神秘主义,因为它通常会掩盖错误。 我们必须认真研究它是如何运作的。 分类 它有什么影响? LC_COLLATE .

最后我会尝试回答以下问题:

  • 为什么女性姓氏排序不正确?
  • 为什么 LANG=ru_RU.CP1251 结果是等价的 朗=C
  • 为什么 分类 и 加入 关于排序字符串顺序的不同想法
  • 为什么我所有的例子都有错误?
  • 最后如何根据您的喜好对字符串进行排序

以 Unicode 排序

第一站将是第10号技术报告,题为 Unicode 排序规则算法 在线 unicode.org。 该报告包含很多技术细节,所以让我简要总结一下主要想法。

整理 —“比较”字符串是任何排序算法的基础。 算法本身可能有所不同(“冒泡”、“合并”、“快速”),但它们都会使用一对字符串的比较来确定它们出现的顺序。

用自然语言对字符串进行排序是一个相当复杂的问题。 即使在最简单的单字节编码中,字母表中的字母顺序(即使在某种程度上与英语拉丁字母表不同)也将不再与编码这些字母的数值顺序一致。 所以在德语字母表中这个字母 Ö 站在之间 О и P,并且在编码中 CP850 她介于 ÿ и Ü.

您可以尝试从特定编码中抽象出来,并考虑按某种顺序排列的“理想”字母,就像 Unicode 中所做的那样。 编码 UTF8编码, UTF16编码 或一字节 KOI8-R (如果需要 Unicode 的有限子集)将给出字母的不同数字表示,但引用基表的相同元素。

事实证明,即使我们从头开始构建符号表,我们也无法为其分配通用符号顺序。 在使用相同字母的不同国家字母表中,这些字母的顺序可能不同。 例如,在法语中 Æ 将被视为连字并按字符串排序 AE。 挪威语 Æ 将是一个单独的字母,位于 Z。 顺便说一下,除了像这样的连字 Æ 有用几个符号写的字母。 所以在捷克语字母表中有一个字母 Ch,介于 H и I.

除了字母差异之外,还有其他民族传统也会影响排序。 特别是,出现了一个问题:由大写字母和小写字母组成的单词在字典中应该以什么顺序出现? 标点符号的使用也可能影响排序。 在西班牙语中,疑问句的开头使用倒问号(你喜欢音乐吗?)。 在这种情况下,很明显疑问句不应该被分组到字母表之外的单独簇中,但是如何对带有其他标点符号的行进行排序呢?

我不会详细讨论用与欧洲语言截然不同的语言对字符串进行排序。 请注意,在从右到左或从上到下书写方向的语言中,行中的字符很可能按阅读顺序存储,甚至非字母书写系统也有自己的逐字符排序行的方式。 例如,象形文字可以按样式排序(汉字按键) 或按发音。 老实说,我不知道表情符号应该如何排列,但你也可以为它们想出一些东西。

基于上面列出的特征,制定了基于Unicode表比较字符串的基本要求:

  • 字符串的比较不依赖于字符在码表中的位置;
  • 形成单个字符的字符序列被简化为规范形式(A + 顶部的圆圈与 Å);
  • 比较字符串时,会在字符串的上下文中考虑一个字符,并在必要时将其与其邻居组合成一个比较单元(Ch 捷克语)或分为几个(Æ 法语);
  • 所有国家特征(字母、大写/小写、标点符号、书写顺序类型)必须按照手动分配的顺序(表情符号)进行配置;
  • 比较不仅对于排序很重要,而且在许多其他地方也很重要,例如指定行范围(将 {A... z} 替换为 打坏);
  • 比较应该相当快地完成。

此外,该报告的作者制定了算法开发人员不应依赖的比较属性:

  • 比较算法不应要求每种语言都有单独的字符集(俄语和乌克兰语共享大多数西里尔字母字符);
  • 比较不应依赖于 Unicode 表中的字符顺序;
  • 字符串权重不应该是字符串的属性,因为不同文化背景中的同一字符串可能具有不同的权重;
  • 合并或拆分时行权重可能会发生变化(从 x < y 它不遵循那个 xz < yz);
  • 从排序算法的角度来看,具有相同权重的不同字符串被认为是相等的。 引入此类字符串的额外排序是可能的,但可能会降低性能;
  • 在重复排序过程中,具有相同权重的行可能会被交换。 鲁棒性是特定排序算法的属性,而不是字符串比较算法的属性(参见上一段);
  • 随着文化传统的完善/变化,排序规则可能会随着时间而改变。

还规定比较算法对正在处理的字符串的语义一无所知。 因此,仅由数字组成的字符串不应与数字进行比较,并且在英文名称列表中,文章 (甲壳虫乐队).

为了满足所有这些要求,提出了多级(实际上是四级)表排序算法。

以前,字符串中的字符被简化为规范形式并分组为比较单元。 每个比较单元都被分配了与多个比较级别相对应的多个权重。 比较单元的权重是可以进行或多或少比较的有序集合的元素(在本例中为整数)。 特殊意义 忽略 (0x0)表示在相应的比较级别该单元不参与比较。 使用相应级别的权重,可以将字符串的比较重复多次。 在每一级,依次比较两行比较单元的权重。

在不同民族传统的算法的不同实现中,系数的值可能会有所不同,但Unicode标准包含一个基本的权重表 - “默认 Unicode 排序规则元素表” (杜克大学)。 我想指出设置变量 LC_COLLATE 实际上是字符串比较函数中权重表选择的指示。

加权系数 杜克大学 安排如下:

  • 在第一级,所有字母都被简化为相同的大小写,变音符号被丢弃,标点符号(并非全部)被忽略;
  • 在第二级,仅考虑变音符号;
  • 在第三级,仅考虑情况;
  • 在第四级,仅考虑标点符号。

比较分多次进行:首先,比较第一级的系数; 如果权重一致,则与第二级权重进行重复比较; 然后也许是第三个和第四个。

当行包含具有不同权重的匹配比较单位时,比较结束。 在所有四个级别上具有相同权重的行被视为彼此相等。

这个算法(带有一堆额外的技术细节)给了第 10 号报告的名称 - 《Unicode 校对算法》 (加州大学洛杉矶分校).

这是我们示例中的排序行为变得更加清晰的地方。 最好将它与 Unicode 标准进行比较。

测试实施 加州大学洛杉矶分校 有一个特殊的 测试, 使用 权重文件,实施 杜克大学。 您可以在秤文件中找到各种有趣的东西。 比如麻将、欧式多米诺骨牌的顺序,以及一副牌中花色的顺序(符号 1F000 并进一步)。 纸牌花色按照桥牌-PCBT的规则放置,花色中的牌的顺序为T、2,3、XNUMX...K。

手动检查行是否正确排序 杜克大学 这将是相当乏味的,但是,对我们来说幸运的是,有一个用于使用 Unicode 的库的示例性实现 - ”Unicode的国际组件“(ICU).

在该图书馆的网站上,开发于 IBM,有演示页面,包括 字符串比较算法页面。 我们使用默认设置输入测试线,你瞧,我们得到了完美的俄语排序。

Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Иванова Алла;адвокат

顺便说一句,网站 ICU 您可以在处理标点符号时找到比较算法的说明。 在示例中 整理常见问题解答 撇号和连字符将被忽略。

Unicode 帮助了我们,但要寻找奇怪行为的原因 分类 в Linux 将不得不去其他地方。

glibc中的排序

快速查看实用程序源代码 分类 из GNU 核心实用程序 表明在实用程序本身中,本地化归结为打印变量的当前值 LC_COLLATE 在调试模式下运行时:

$ sort --debug buhg.txt > buhg.srt
sort: using ‘en_US.UTF8’ sorting rules

使用标准函数执行字符串比较 斯特科尔,这意味着所有有趣的东西都在图书馆里 glibc的.

维基 项目 glibc的 专用于字符串比较 一段。 从这一段可以看出,在 glibc的 排序基于我们已知的算法 加州大学洛杉矶分校 (Unicode 排序规则算法)和/或接近它的标准 ISO 14651 (国际字符串排序和比较)。 关于最新标准,需要注意的是网站上 标准.iso.org ISO 14651 官方声明公开可用,但相应的链接指向不存在的页面。 谷歌返回了几个页面,其中包含指向官方网站的链接,这些网站提供以一百欧元购买标准电子版的链接,但在搜索结果的第三页或第四页上也有直接链接 PDF。 一般来说,该标准实际上与 加州大学洛杉矶分校,但是读起来比较枯燥,因为它没有包含字符串排序的国家特征的清晰示例。

最有趣的信息 维基 有一个链接到 错误跟踪器 讨论了字符串比较的实现 glibc的。 从讨论中可以得知 glibc的 用于比较字符串 ISO个人餐桌 通用模板表 (CTT),其地址可以在应用程序中找到 A 标准 ISO 14651 。 2000 年至 2015 年间,该表 glibc的 没有维护者,并且与当前版本的标准有很大不同(至少在外部)。 从2015年到2018年,对新版本的表进行了适应,现在您有机会在现实生活中遇到新版本的表(CentOS 8的)和旧的(CentOS 7的).

现在我们已经掌握了有关算法和辅助表的所有信息,我们可以回到原来的问题并了解如何在俄语语言环境中正确对字符串进行排序。

ISO 14651 / 14652

我们感兴趣的表的源代码 CTT 在大多数发行版上 Linux 在目录中 /usr/share/i18n/locales/。 表本身在文件中 iso14651_t1_common。 这就是文件指令 复制 iso14651_t1_common 包含在文件中 ISO14651_t1,进而包含在国家档案中,包括 EN_US и ru_RU。 在大多数发行版上 Linux 所有源文件都包含在基本安装中,但如果它们不存在,您将必须安装发行版中的附加包。

文件结构 ISO14651_t1 可能看起来非常冗长,并且构造名称的规则并不明显,但如果你仔细看一下,一切都非常简单。 标准中描述了该结构 ISO 14652 ,可以从网站下载一份副本 打开-std.org。 可以读取文件格式的另一个描述 规格 POSIX开放集团。 作为阅读标准的替代方法,您可以研究该函数的源代码 校对读 в glibc/locale/programs/ld-collat​​e.c.

文件结构如下所示:

默认情况下,该字符用作转义字符,#字符后面的行尾为注释。 这两个符号都可以重新定义,这是在新版本的表中所做的:

escape_char /
comment_char %

该文件将包含以下格式的令牌 или (其中 x - 十六进制数字)。 这是编码中 Unicode 代码点的十六进制表示 UCS-4 (UTF-32)。 尖括号中的所有其他元素(包括 , 等)被认为是简单的字符串常量,在上下文之外没有什么意义。

LC_COLLATE 告诉我们接下来开始描述字符串比较的数据。

首先,指定比较表中权重的名称和符号组合的名称。 一般来说,两种类型的名称属于两个不同的实体,但在实际文件中它们是混合的。 权重的名称由关键字指定 整理符号 (比较字符)因为在比较时,具有相同权重的 Unicode 字符将被视为等效字符。

当前文件修订版中该部分的总长度约为 900 行。 我从几个地方举了例子来展示名称和几种语法类型的任意性。

LC_COLLATE

collating-symbol <RES-1>
collating-symbol <BLK>
collating-symbol <MIN>
collating-symbol <WIDE>
...
collating-symbol <ARABIC>
collating-symbol <ETHPC>
collating-symbol <OSMANYA>
...
collating-symbol <S1D000>..<S1D35F>
collating-symbol <SFFFF> % Guaranteed largest symbol value. Keep at end of this list
...
collating-element <U0413_0301> from "<U0413><U0301>"
collating-element <U0413_0341> from "<U0413><U0341>"

  • 整理符号 记录一个字符串 奥斯曼尼亚 在音阶名称表中
  • 整理符号.. 注册由前缀组成的名称序列 S 和十六进制数字后缀 1D0001天35楼.
  • FFFF в 整理符号 看起来像一个很大的十六进制无符号整数,但是 这只是一个可能看起来像的名字
  • 名称 表示编码中的代码点 UCS-4
  • 整理元素从 ” ” 为一对 Unicode 点注册一个新名称。

一旦定义了权重的名称,就指定了实际的权重。 由于只有大于小于的关系在比较中才重要,因此权重由简单的列表名称序列确定。 首先列出“较轻”的重量,然后列出“较重”的重量。 让我提醒您,每个 Unicode 字符都分配有四种不同的权重。 在这里,它们被组合成一个有序序列。 理论上,任何符号名称都可以在四个级别中的任何一个级别使用,但评论表明开发人员在心里将名称分为不同级别。

% Symbolic weight assignments

% Third-level weight assignments
<RES-1>
<BLK>
<MIN>
<WIDE>
...
% Second-level weight assignments
<BASE>
<LOWLINE> % COMBINING LOW LINE
<PSILI> % COMBINING COMMA ABOVE
<DASIA> % COMBINING REVERSED COMMA ABOVE
...
% First-level weight assignments
<S0009> % HORIZONTAL TABULATION 
<S000A> % LINE FEED
<S000B> % VERTICAL TABULATION
...
<S0434> % CYRILLIC SMALL LETTER DE
<S0501> % CYRILLIC SMALL LETTER KOMI DE
<S0452> % CYRILLIC SMALL LETTER DJE
<S0503> % CYRILLIC SMALL LETTER KOMI DJE
<S0453> % CYRILLIC SMALL LETTER GJE
<S0499> % CYRILLIC SMALL LETTER ZE WITH DESCENDER
<S0435> % CYRILLIC SMALL LETTER IE
<S04D7> % CYRILLIC SMALL LETTER IE WITH BREVE
<S0454> % CYRILLIC SMALL LETTER UKRAINIAN IE
<S0436> % CYRILLIC SMALL LETTER ZHE

最后是实际重量表。

权重部分包含在关键字行中 订单开始时间 и 订单结束。 额外选项 订单开始时间 确定在每个比较级别扫描行的方向。 默认设置是 前锋。 该部分的主体由包含符号代码及其四个权重的行组成。 字符代码可以由字符本身、代码点或先前定义的符号名称来表示。 权重也可以赋予符号名称、代码点或符号本身。 如果使用代码点或字符,则它们的权重与代码点的数值(Unicode 表中的位置)相同。 未明确指定的字符(据我所知)被视为分配给表,其主要权重与 Unicode 表中的位置相匹配。 特殊重量值 忽略 意味着该符号在适当的比较级别被忽略。

为了演示音阶的结构,我选择了三个相当明显的片段:

  • 完全被忽略的字符
  • 前两级中相当于数字三的符号
  • 西里尔字母的开头,不包含变音符号,因此主要按第一级和第三级排序。

order_start forward;forward;forward;forward,position
<U0000> IGNORE;IGNORE;IGNORE;IGNORE % NULL (in 6429)
<U0001> IGNORE;IGNORE;IGNORE;IGNORE % START OF HEADING (in 6429)
<U0002> IGNORE;IGNORE;IGNORE;IGNORE % START OF TEXT (in 6429)
...
<U0033> <S0033>;<BASE>;<MIN>;<U0033> % DIGIT THREE
<UFF13> <S0033>;<BASE>;<WIDE>;<UFF13> % FULLWIDTH DIGIT THREE
<U2476> <S0033>;<BASE>;<COMPAT>;<U2476> % PARENTHESIZED DIGIT THREE
<U248A> <S0033>;<BASE>;<COMPAT>;<U248A> % DIGIT THREE FULL STOP
<U1D7D1> <S0033>;<BASE>;<FONT>;<U1D7D1> % MATHEMATICAL BOLD DIGIT THREE
...
<U0430> <S0430>;<BASE>;<MIN>;<U0430> % CYRILLIC SMALL LETTER A
<U0410> <S0430>;<BASE>;<CAP>;<U0410> % CYRILLIC CAPITAL LETTER A
<U04D1> <S04D1>;<BASE>;<MIN>;<U04D1> % CYRILLIC SMALL LETTER A WITH BREVE
<U0430_0306> <S04D1>;<BASE>;<MIN>;<U04D1> % CYRILLIC SMALL LETTER A WITH BREVE
...
<U0431> <S0431>;<BASE>;<MIN>;<U0431> % CYRILLIC SMALL LETTER BE
<U0411> <S0431>;<BASE>;<CAP>;<U0411> % CYRILLIC CAPITAL LETTER BE
<U0432> <S0432>;<BASE>;<MIN>;<U0432> % CYRILLIC SMALL LETTER VE
<U0412> <S0432>;<BASE>;<CAP>;<U0412> % CYRILLIC CAPITAL LETTER VE
...
order_end

现在您可以返回到对本文开头的示例进行排序。 埋伏就在权重表的这一部分:

<U0020> IGNORE;IGNORE;IGNORE;<U0020> % SPACE
<U0021> IGNORE;IGNORE;IGNORE;<U0021> % EXCLAMATION MARK
<U0022> IGNORE;IGNORE;IGNORE;<U0022> % QUOTATION MARK
...

可以看出,该表中的标点符号来自表 ASCII码 比较字符串时(包括空格)几乎总是被忽略。 唯一的例外是除了在匹配位置找到的标点符号之外所有内容都匹配的行。 我的示例中的比较算法行(排序后)如下所示:

АбакановМихаилмаляр
ЁлкинаЭллакрановщица
ИвановаАлламаляр
ИвановАндрейслесарь

考虑到在音阶表中,俄语中的大写字母在小写字母之后(在第三级 比...更重 ),排序看起来绝对正确。

设置变量时 LC_COLLATE=C 加载一个特殊的表来指定逐字节比较

static const uint32_t collseqwc[] =
{
  8, 1, 8, 0x0, 0xff,
  /* 1st-level table */
  6 * sizeof (uint32_t),
  /* 2nd-level table */
  7 * sizeof (uint32_t),
  /* 3rd-level table */
  L'x00', L'x01', L'x02', L'x03', L'x04', L'x05', L'x06', L'x07',
  L'x08', L'x09', L'x0a', L'x0b', L'x0c', L'x0d', L'x0e', L'x0f',

...
  L'xf8', L'xf9', L'xfa', L'xfb', L'xfc', L'xfd', L'xfe', L'xff'
};

由于在 Unicode 中代码点 Ё 位于 A 之前,因此字符串会相应地排序。

文本和二进制表

显然,字符串比较是一个极其常见的操作,而表解析 CTT 这是一个相当昂贵的过程。 为了优化对表的访问,使用以下命令将其编译为二进制形式 本地定义.

团队 本地定义 接受带有国家特征表的文件作为参数(选项 -i),其中所有字符均由 Unicode 点表示,以及 Unicode 点与特定编码的字符之间的对应关系文件(选项 -f)。 这项工作的结果是,为区域设置创建了二进制文件,其名称在最后一个参数中指定。

格里布 支持两种二进制文件格式:“传统”和“现代”。

传统格式意味着语言环境的名称是子目录的名称 /usr/lib/语言环境/。 该子目录存放二进制文件 LC_COLLATE, LC_CTYPE, LC_TIME 等等。 文件 LC_IDENTIFICATION 包含区域设置的正式名称(可能与目录名称不同)和注释。

现代格式涉及将所有语言环境存储在一个存档中 /usr/lib/locale/区域设置存档,它被映射到所有进程的虚拟内存 glibc的。 现代格式中的语言环境名称受到一些规范化的影响 - 只有数字和字母缩减为小写字母仍保留在编码名称中。 所以 ru_RU.KOI8-R,将另存为 ru_RU.koi8r.

输入文件在当前目录以及目录中搜索 /usr/share/i18n/locales/ и /usr/share/i18n/charmaps/ 对于文件 CTT 和编码文件,分别。

例如,命令

localedef -i ru_RU -f MAC-CYRILLIC ru_RU.MAC-CYRILLIC

将编译文件 /usr/share/i18n/locales/ru_RU 使用编码文件 /usr/share/i18n/charmaps/MAC-CYRILLIC.gz 并将结果保存在 /usr/lib/locale/区域设置存档 以...之名 ru_RU.maccyrillic

如果您设置变量 LANG =的en_US.UTF-8glibc的 将在以下文件和目录序列中查找语言环境二进制文件:

/usr/lib/locale/locale-archive
/usr/lib/locale/en_US.UTF-8/
/usr/lib/locale/en_US/
/usr/lib/locale/enUTF-8/
/usr/lib/locale/en/

如果某个语言环境同时以传统格式和现代格式出现,则优先考虑现代格式。

您可以使用以下命令查看编译的语言环境列表 locale -a.

准备比较表

现在,有了这些知识,您就可以创建自己理想的字符串比较表。 该表应正确比较俄语字母,包括字母 Ё,同时根据表考虑标点符号 ASCII码.

准备自己的排序表的过程包括两个阶段:编辑权重表并使用命令将其编译为二进制形式 本地定义.

为了以最小的编辑成本调整比较表,格式为 ISO 14652 提供了用于调整现有表格权重的部分。 该部分以关键字开头 之后重新排序 并指示更换后的位置。 该部分以该行结束 重新排序结束。 如果需要更正表的多个部分,则为每个这样的部分创建一个部分。

我复制了文件的新版本 iso14651_t1_common и ru_RU 从存储库 glibc的 到我的主目录 ~/.local/share/i18n/locales/ 并稍微编辑了该部分 LC_COLLATE в ru_RU。 新版本的文件与我的版本完全兼容 glibc的。 如果您想使用旧版本的文件,则必须更改符号名称以及表中替换开始的位置。

LC_COLLATE
% Copy the template from ISO/IEC 14651
copy "iso14651_t1"
reorder-after <U000D>
<U0020> <S0020>;<BASE>;<MIN>;<U0020> % SPACE
<U0021> <S0021>;<BASE>;<MIN>;<U0021> % EXCLAMATION MARK
<U0022> <S0022>;<BASE>;<MIN>;<U0022> % QUOTATION MARK
...
<U007D> <S007D>;<BASE>;<MIN>;<U007D> % RIGHT CURLY BRACKET
<U007E> <S007E>;<BASE>;<MIN>;<U007E> % TILDE
reorder-end
END LC_COLLATE

事实上,有必要更改其中的字段 LC_IDENTIFICATION 这样他们就可以指向语言环境 ru_MY,但在我的示例中这不是必需的,因为我从语言环境搜索中排除了存档 区域设置存档.

本地定义 通过变量处理我的文件夹中的文件 国际化路径 您可以添加额外的目录来搜索输入文件,并且可以将保存二进制文件的目录指定为带斜杠的路径:

$> I18NPATH=~/.local/share/i18n localedef -i ru_RU -f UTF-8 ~/.local/lib/locale/ru_MY.UTF-8

POSIX 表明在 您可以将绝对路径写入包含区域设置文件的目录,以正斜杠开头,但是 glibc的 в Linux 所有路径均从基目录开始计算,可以通过变量覆盖 位置路径。 安装后 LOCPATH=~/.local/lib/locale/ 所有与本地化相关的文件将仅在我的文件夹中搜索。 具有变量集的语言环境存档 位置路径 被忽略。

这是决定性的测试:

$> LANG=ru_MY.UTF-8 LOCPATH=~/.local/lib/locale/ sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Иванова Алла;адвокат

万岁! 我们做到了!

有些错误

我已经回答了开头提出的关于字符串排序的问题,但是仍然有几个关于错误的问题 - 可见的和不可见的。

让我们回到最初的问题。

还有节目 分类 和程序 加入 使用相同的字符串比较函数 glibc的。 这是怎么发生的 加入 在按命令排序的行上出现排序错误 分类 在语言环境中 en_US.UTF-8? 答案很简单: 分类 比较整个字符串,并且 加入 仅比较键,默认情况下该键是字符串的开头到第一个空白字符。 在我的示例中,这会导致错误消息,因为行中第一个单词的排序与完整行的排序不匹配。

语言环境 “C” 保证在排序字符串中,直到第一个空格的初始子字符串也将被排序,但这只能掩盖错误。 可以选择在没有错误消息的情况下给出不正确的文件合并结果的数据(具有相同姓氏但名字不同的人)。 如果我们想要 加入 按全名合并文件行,那么正确的方法是显式指定字段分隔符并按键字段排序,而不是按整行排序。 在这种情况下,合并将正确进行,并且在任何语言环境中都不会出现错误:

$> sort -t ; -k 1 buhg.txt > buhg.srt
$> sort -t ; -k 1 mail.txt > mail.srt
$> join -t ; buhg.srt mail.srt > result

编码中成功执行示例 CP1251 包含另一个错误。 事实是,在我所知的所有发行版中 Linux 软件包缺少已编译的语言环境 ru_RU.CP1251。 如果未找到编译的语言环境,则 分类 默默地使用逐字节比较,这就是我们观察到的。

顺便说一句,还有另一个与编译语言环境无法访问相关的小故障。 团队 LOCPATH=/tmp 语言环境 -a 将给出所有语言环境的列表 区域设置存档,但是设置了变量 位置路径 对于所有程序(包括大多数 当地) 这些区域设置将不可用。

$> LOCPATH=/tmp locale -a | grep en_US
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_COLLATE to default locale: No such file or directory
en_US
en_US.iso88591
en_US.iso885915
en_US.utf8

$> LC_COLLATE=en_US.UTF-8 sort --debug
sort: using ‘en_US.UTF-8’ sorting rules

$> LOCPATH=/tmp LC_COLLATE=en_US.UTF-8 sort --debug
sort: using simple byte comparison

结论

如果你是一个习惯于认为字符串是一组字节的程序员,那么你的选择 LC_COLLATE=C.

如果您是语言学家或字典编译器,那么您最好在您的语言环境中进行编译。

如果您是一个简单的用户,那么您只需要习惯该命令 ls -a 输出以点开头的文件与以字母开头的文件混合,以及 午夜指挥官,它使用其内部函数对名称进行排序,将以点开头的文件放在列表的开头。

引用

报告10号Unicode排序算法

unicode.org 上的字符权重

ICU — IBM 的用于使用 Unicode 的库的实现。

排序测试使用 ICU

字符权重为 ISO 14651

带刻度的文件格式说明 ISO 14652

中字符串比较的讨论 glibc的

来源: habr.com

添加评论