在 unicode 文本上使用 uniq

evb*_*evb 5 sort unicode uniq

我想从带有叙利亚文字的文件中删除重复的行。源文件有 3 行,第 1 行和第 3 行相同。

$ cat file.txt 
????
????
????
Run Code Online (Sandbox Code Playgroud)

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

$ cat file.txt | sort | uniq -c
      3 ????
Run Code Online (Sandbox Code Playgroud)

将语言环境显式设置为叙利亚语也无济于事。

$ LC_COLLATE=syr_SY.utf8 cat file.txt | sort | uniq -c      
     3 ????
Run Code Online (Sandbox Code Playgroud)

为什么会这样?如果重要的话,我正在使用 Kubuntu 18 和 bash。

Sté*_*las 8

uniq在 Ubuntu 上找到的 GNU 实现-c不报告连续相同行的计数,而是报告排序相同¹的连续行计数。

GNU 系统上的大多数国际语言环境都有一个错误,即许多完全不相关的字符已被定义为相同的排序顺序,因为它们的排序顺序根本没有定义。大多数其他操作系统确保所有字符具有不同的排序顺序。

$ expr ? = ?
1
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

请注意,在:

LC_COLLATE=syr_SY.utf8 cat file.txt | sort | uniq -c
Run Code Online (Sandbox Code Playgroud)

您只是为cat命令设置 LC_COLLATE 变量(这不会影响它输出文件内容的方式,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
Run Code Online (Sandbox Code Playgroud)

所以用:

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

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

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

<file LC_ALL=C sort | LC_ALL=C uniq -c
Run Code Online (Sandbox Code Playgroud)

或者

(export LC_ALL=C; <file sort | uniq -c)
Run Code Online (Sandbox Code Playgroud)

正如@isaac 已经显示的那样。


¹ 请注意,符合 POSIX 的uniq实现并不是要使用语言环境的整理算法来比较字符串,而是要进行字节到字节的相等比较。这在 2018 版标准中得到了进一步澄清(请参阅相应的 Austin 组错误)。但是 GNUuniq目前确实使用strcoll(),即使在POSIXLY_CORRECT; 它还有一个-i不区分大小写的比较选项,具有讽刺意味的是它不使用区域设置信息并且只能在 ASCII 输入上正常工作


ImH*_*ere 6

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

$ ( LC_ALL=C sort syriac.txt | LC_ALL=C uniq -c )
      2 ????
      1 ????
Run Code Online (Sandbox Code Playgroud)

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

$ ( 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                                     ..
Run Code Online (Sandbox Code Playgroud)

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

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

一个等效的解决方案是:

$ ( export LC_COLLATE=C.UTF-8; <syriac.txt sort | uniq -c )
Run Code Online (Sandbox Code Playgroud)

基本问题是来自叙利亚语的字符(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-2639-2 代码的官方列表中分配了一个三字母代码

国家代码(ISO 3166)通常是两个字母组成的代码,而且也应该SY。ISO 3166 国家/地区代码列表

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

当 Unicode 代码点没有明确定义的整理顺序时,它们可能变得完全相同:未定义。这就是这里发生的事情。

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

$ echo $(cat syriac.txt | grep -oP '\X' | sort)
? ? ? ? ? ? ? ? ? ? ? ? 
Run Code Online (Sandbox Code Playgroud)

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

$ echo $(cat syriac.txt | grep -oP '\X' | sort -u )
?
Run Code Online (Sandbox Code Playgroud)

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

$ a=?
$ b=?
$ [[ $a == [=$b=] ]] && echo yes
yes
Run Code Online (Sandbox Code Playgroud)

这意味着,无功a值是在相同的排序规则位置[=…=]变种的b值。

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

$ echo $(cat syriac.txt | grep -oP '\X' | LC_COLLATE=C.UTF-8 sort -u )
? ? ? ? ?
Run Code Online (Sandbox Code Playgroud)