在 unicode 文本上使用 uniq

在 unicode 文本上使用 uniq

我想从文件中删除重复的行,其中包含以下内容叙利亚语脚本。源文件有 3 行,第一行和第三行相同。

$ cat file.txt 
ܐܒܘܢ
ܢܗܘܐ
ܐܒܘܢ

当我使用sortand时uniq,结果假定所有 3 行都是相同的,这是错误的:

$ cat file.txt | sort | uniq -c
      3 ܐܒܘܢ

明确将语言环境设置为叙利亚语也没有帮助。

$ LC_COLLATE=syr_SY.utf8 cat file.txt | sort | uniq -c      
     3 ܐܒܘܢ

为什么会发生这种情况?我正在使用 Kubuntu 18 和 bash,如果这很重要的话。

答案1

uniqUbuntu 上的GNU 实现,带有-c,不报告连续的计数完全相同的行,但排序相同的连续行的数量。

GNU 系统上的大多数国际语言环境都存在这样的错误:许多完全不相关的字符已使用相同的排序顺序定义,其中大多数是因为它们的排序顺序根本没有定义。大多数其他操作系统确保所有字符都有不同的排序顺序。

$ expr ܐ = ܒ
1

(expr=运算符,对于非数字参数,如果操作数排序相同则返回 1,否则返回 0)。

ar_SY.UTF-8这与or相同en_GB.UTF-8

您需要的是一个区域设置,其中这些字符已被赋予不同的排序顺序。如果 Ubuntu 有叙利亚语言的区域设置,您可能会期望这些字符被赋予不同的排序顺序,但 Ubuntu 没有这样的区域设置。

您可以查看输出locale -a以获取支持的语言环境列表。您可以通过运行dpkg-reconfigure localesas 来启用更多区域设置root。您还可以localedef根据 中的定义文件手动定义更多语言环境/usr/share/i18n/locales,但您不会在那里找到叙利亚语的数据。

请注意,在:

LC_COLLATE=syr_SY.utf8 cat file.txt | sort | uniq -c

您只需为命令设置 LC_COLLATE 变量cat(这不会影响它输出文件内容的方式,cat不关心排序规则,甚至不关心字符编码,因为它不是文本实用程序)。您想为sort和两者设置它uniq。您还需要设置LC_CTYPE具有 UTF-8 字符集的区域设置。

由于您的系统没有区域设置,因此这与使用区域设置(默认区域设置)syr_SY.utf8相同。C

实际上,这里的 C 语言环境或 C.UTF-8 可能是您想要使用的语言环境。

在这些语言环境中,排序规则顺序基于代码点、C.UTF-8 的 Unicode 代码点、C 的字节值,但最终与具有该属性的 UTF-8 字符编码相同。

$ LC_ALL=C expr ܐ = ܒ
0
$ LC_ALL=C.UTF-8 expr ܐ = ܒ
0

所以:

(export LANG=ar_SY.UTF-8 LC_COLLATE=C.UTF-8 LANGUAGE=syr:ar:en
 unset LC_ALL
 sort <file | uniq -c)

您将拥有一个以 UTF-8 作为字符集的 LC_CTYPE、基于代码点的排序顺序以及与您所在区域相关的其他设置,例如叙利亚语或阿拉伯语的错误消息(如果 GNU coreutilssortuniq消息已翻译为这些语言)语言(他们还没有)。

如果你不关心这些其他设置,使用起来同样简单(也更便携):

<file LC_ALL=C sort | LC_ALL=C uniq -c

或者

(export LC_ALL=C; <file sort | uniq -c)

正如@isaac 已经表明的那样。


1 请注意,符合 POSIX 的uniq实现并不意味着使用区域设置的排序算法来比较字符串,而是进行字节到字节的相等比较。 2018 年版标准进一步明确了这一点(参见相应的奥斯汀组错误)。但 GNUuniq目前确实使用strcoll(),甚至在POSIXLY_CORRECT;它还有一个-i不区分大小写的比较选项,讽刺的是,它不使用区域设置信息,并且仅在 ASCII 输入上正确工作

答案2

一个(简单的)便携式解决方案:

$ ( LC_ALL=C sort syriac.txt | LC_ALL=C uniq -c )
      2 ܐܒܘܢ
      1 ܢܗܘܐ

对于那些没有可以呈现叙利亚文字的字体的人:

$ ( LC_ALL=C sort syriac.txt | LC_ALL=C uniq -c ) | xxd
00000000: 2020 2020 2020 3220 dc90 dc92 dc98 dca2        2 ........
00000010: 0a20 2020 2020 2031 20dc a2dc 97dc 98dc  .      1 .......
00000020: 900a                                     ..

编辑 这更接近于黑客攻击,而不是真正的解决方案。它的工作原理是使用各个字节的值而不是区域设置表给出的排序规则来处理每一行sortuniq要使用的等效语言环境(因为 UTF-8“代码点排序顺序”与“字节值排序顺序”的顺序相同)是C.UTF-8

这在大多数系统 AFAICT 中都有效。

一个等效的解决方案是:

$ ( export LC_COLLATE=C.UTF-8; <syriac.txt sort | uniq -c )

基本问题是来自叙利亚语的字符(Unicode 代码点U+0700–U+074F 叙利亚语U+0860-U+086F 叙利亚语补充) 尚未设置任何排序规则排序顺序。

这是内部语言环境定义文件/usr/share/i18n/locales(debian/ubuntu) 的问题,甚至没有在less /usr/share/i18n/SUPPORTED.这意味着该语言的信息需要报告给 Debian i18n 并内置到有效的语言环境文件中。

通常,区域设置名称通常采用“ll_CC”形式。这里“ll”是 ISO 639 两个字母的语言代码,“CC”是 ISO 3166 两个字母的国家/地区代码。叙利亚语(西方变体)Syrj

叙利亚语已在 ISO 639-2 中分配了一个三字母代码639-2 代码的官方列表

国家/地区代码 (ISO 3166) 通常是两个字母的代码可能应该是SY。ISO 3166 国家/地区代码列表

仅设置与区域设置相关的一个或所有环境变量是不够的,并且可能会失败(正如您的情况所发生的那样),因为所有表都丢失了。这些表设置了月份名称、工作日、年份公式、时间格式、货币格式、报告错误的语言(如果有翻译)等。请阅读:我应该将区域设置设置为什么?这样做会产生什么影响?

当 Unicode 代码点没有显式定义的排序规则时,它们可能会变得完全相同:未定义。这就是这里发生的情况。

我们可能会列出您文件中的代码点(仅使用一个示例点):

$ echo $(cat syriac.txt | grep -oP '\X' | sort)
ܐ ܒ ܘ ܢ ܢ ܗ ܘ ܐ ܐ ܒ ܘ ܢ 

但如果我们尝试只获取唯一值,所有值都会被删除:

$ echo $(cat syriac.txt | grep -oP '\X' | sort -u )
ܐ

这是因为所有字符都具有相同的排序规则值(权重):

$ a=ܐ
$ b=ܒ
$ [[ $a == [=$b=] ]] && echo yes
yes

这意味着 var值与 var值a处于相同的排序位置。[=…=]b

相反,这列出了非重复字符:

$ echo $(cat syriac.txt | grep -oP '\X' | LC_COLLATE=C.UTF-8 sort -u )
ܐ ܒ ܗ ܘ ܢ

答案3

第一组LC_CTYPE

$ export LC_CTYPE=syr_SY.utf8
$ <infile sort |uniq -c
      2 ܐܒܘܢ
      1 ܢܗܘܐ

相关内容