Unix特殊情况下的区分大小写的UTF-8排序

3
我发现了一个关于我的问题的帖子(shell - Different versions of UNIX sort handle case differently),但是它给出了“相反”的答案。
我已经尝试过更改LANG变量的值,但似乎找不到达成目标的值。
为了举例说明:
abc a
Abc d
Abc b
abc e
abæ g

需要进行排序:

abc a
abc c
Abc b
Abc d
abæ g

不是这个(这是我目前得到的内容):

Abc b
Abc d
abc a
abc c
abæ g

也不是这个(当我进行大小写不敏感的排序时得到的结果):

abc a
Abc b
abc c
Abc d
abæ g

换句话说:我想要按列进行区分大小写的排序,其中首字母大写的单词不会排在最前面,同一个单词的大小写版本不会根据第二列混合在一起进行排序。
请注意,我需要UTF-8敏感的排序(在本例中,我使用了丹麦字母“æ”,它在字母表中的位置是这样的:“...vwxyzæøå”)。
我正在使用以下两列进行排序:
sort test.txt -k1,1 -k2,2

有什么方法可以不用脚本来完成这个任务吗?
1个回答

6

在第一列中混合大小写的内容会根据第二列而混合在一起,但是不希望出现这种情况,因此需要进行大小写敏感的排序。大小写不敏感的排序视为相同的那些共享一个折叠大小写的内容。

需要排序的Unicode记录集:

abc a
Abc d
Abc b
abc e
abæ g

当然,这当中就包括了以下内容:
abæ g
abc a
Abc b
Abc d
abc e

这是因为在这五行中,第一个和第二个字母都“相同”(即,它们的大小写折叠后是相同的),所以第一个不同的字母是第三个字母,当然是一个 æ,排在 c 前面,而其他四条记录的第三个字母正好是 c。
对于剩下的行,它们的前三个字母都相同,所以它们的第四个字母是决定性的,现在是 a、b、d、e 的顺序。空格在 Unicode 排序中通常不重要,因为它是字母数字排序而不是代码点排序。我们只考虑字母,除非它们一直到大小写都相同,只有在这种情况下才考虑其他代码点。
这就是 Unicode 排序的工作原理。
Unicode Collation Algorithm 不会注意丹麦排序,除非你要求它这样做。该代码点的默认 DUCET 条目将像æ和å这样的内容放在 a 旁边,ø 放在 o 旁边。OED 将这些条目按以下顺序排序:
 allergist
 allergy
 Allerød
 allers
 allethrin

这是因为 "Allerød" 中的 o 排在 "allergy" 中的 g 之后,并紧随 allers 的 s。仅当其他一切相同时,变音符才有意义。因此,假设有一个 "alleroc",它将位于 "Allerød" 之前;而一个假设中的 "allerog" 将会跟随 "Allerød" 但在 "allers" 之前。
这只是 Unicode 编码中排序的工作原理。斯堪的纳维亚人讨厌它,因为他们认为它应该按照他们个性化的国家系统操作,但 Unicode 不偏向任何特定语言。如果你想使用本地化排序来获取类似丹麦的特定排序,可以这样做:
abc a
Abc b
Abc d
abc e
abæ g

你需要使用指定了丹麦语区域设置的排序,而不是使用破损的POSIX方法,而是使用Unicode方式。
首先,你必须放弃试图使用sort(1)。它比无用还糟:不可靠而且欺骗性的。如果你有Unicode数据,你应该使用Unicode排序,无论是像OED一样未经修改的,还是为你的小村庄修改过的。
为了产生正常的Unicode排序,你必须使用:
#!/usr/bin/env perl
use strict;
use warnings;
use open qw(:std :utf8);
use utf8;

use Unicode::Collate;

my @lines = <<'End_of_Lines' =~ /\S.*\S\n/g;
    abc a
    Abc d
    Abc b
    abc e
    abæ g
End_of_Lines

my $collator = Unicode::Collate->new();
print $collator->sort(@lines);

要获取本地化限制的非默认排序,仅供您使用,您需要:
#!/usr/bin/env perl    
use strict;
use warnings;
use open qw(:std :utf8);
use utf8;

use Unicode::Collate::Locale;

my @lines = <<'End_of_Lines' =~ /\S.*\S\n/g;
    abc a
    Abc d
    Abc b
    abc e
    abæ g
End_of_Lines

my $collator = Unicode::Collate::Locale->new(locale => "da");    
print $collator->sort(@lines);

Unicode::Collate模块自Perl v5.6版本起已经被包含在标准库中。 Unicode::Collate::Locale模块自Perl v5.14版本起已经被包含在标准库中,但是在早期版本上它也可以通过CPAN轻松安装:

 $ sudo perl -MCPAN -e "install Unicode::Collate::Locale"

您必须使用Perl是因为您不能简单地相信供应商的语言环境会按照Unicode排序算法工作,无论是否进行语言环境修改。我从未见过两个不同的系统以相同的方式工作,这意味着每对中至少有一个是错误的,也许两者都是。相比之下,您可以保证UCA在任何地方始终以相同的方式运行。它不关心您的终端可以显示什么,不关心字体,不关心您是否被重定向,不关心您正在运行哪个shell,也不关心您的Aunt Gertrude是否在月份的第5个星期一运行代码。它只管工作,并且在任何情况下都以相同的方式工作。使用UCA,没有替代品。

但是,仅仅因为您使用了UCA并不意味着您需要接受默认排序。 UCA旨在非常适应定制。如果您想要按语言环境排序,这很容易 - 如果该语言环境有CLDR数据,则非常简单。如果您想对书籍和电影标题进行排序,或者对人名进行排序,其中姓氏计数强于名字,所有苏格兰的Mc和Mac名称都排在M-之前,但彼此无关,所有这些都使用UCA非常容易实现。您可以想象的任何事情都可以做到,通常是非常容易的。重点是,使用UCA时,您始终从一个行为开始,无论平台或偏见如何,它都保证以完全相同的方式工作。这意味着当您想要应用自己的定制时,您可以依靠它的工作方式。如果没有这个保证,一切都将丧失。

您可以在此处获取Unix sort(1)程序的预制命令行替代品(好吧,有点像),该程序符合UCA规范here。当然,它不会处理字段,但它确实可以做更多事情。


本来希望能够避免使用脚本,但似乎不可能。 - Woodgnome
Woodgnome:脚本可以给你更多的灵活性,而且它不必太复杂。恐怕即使过去20年,Unix工具也没有跟上Unicode的步伐。就像在只能处理7位字符时,人们想要8位特定于国家的字符集一样。我相信Unicode::Collate基类和Unicode::Collate::Locale派生类可以在Perl 5.6及更早版本上运行超过十年。只是第二个类直到最近才成为标准。它使用CLDR数据。只需进行CPAN安装,您就会没问题了。 - tchrist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接