语言环境在Linux/POSIX中是如何工作的,应用了哪些转换?

语言环境在Linux/POSIX中是如何工作的,应用了哪些转换?,linux,utf-8,posix,locale,gnu-coreutils,Linux,Utf 8,Posix,Locale,Gnu Coreutils,我正在处理大量(我希望是)UTF-8文本文件。我可以使用Ubuntu 13.10(3.11.0-14-generic)和12.04复制它 在调查一个bug时,我遇到了奇怪的行为 $ export LC_ALL=en_US.UTF-8 $ sort part-r-00000 | uniq -d ɥ ɨ ɞ ɧ 251 ɨ ɡ ɞ ɭ ɯ 291 ɢ ɫ ɬ ɜ 301 ɪ ɳ 475 ʈ ʂ 565 $ export LC_ALL=C $ sort part-

我正在处理大量(我希望是)UTF-8文本文件。我可以使用Ubuntu 13.10(3.11.0-14-generic)和12.04复制它

在调查一个bug时,我遇到了奇怪的行为

$ export LC_ALL=en_US.UTF-8   
$ sort part-r-00000 | uniq -d 
ɥ ɨ ɞ ɧ 251
ɨ ɡ ɞ ɭ ɯ       291
ɢ ɫ ɬ ɜ 301
ɪ ɳ     475
ʈ ʂ     565

$ export LC_ALL=C
$ sort part-r-00000 | uniq -d 
$ # no duplicates found
<> P>在运行一个使用C++程序的自定义C++程序时也会出现重复:使用代码> STD::StrugStuts——在使用<代码> EnU.U.UTF-8/COD>区域时,由于复制而失败。 C++对“代码> STD::String < /Cord>和输入/输出”可能不受影响。 为什么在使用UTF-8语言环境时会发现重复项,而在使用C语言环境时却找不到重复项

区域设置对导致此行为的文本进行了哪些转换

编辑:这是一个小例子

$ uniq -D duplicates.small.nfc 
ɢ ɦ ɟ ɧ ɹ       224
ɬ ɨ ɜ ɪ ɟ       224
ɥ ɨ ɞ ɧ 251
ɯ ɭ ɱ ɪ 251
ɨ ɡ ɞ ɭ ɯ       291
ɬ ɨ ɢ ɦ ɟ       291
ɢ ɫ ɬ ɜ 301
ɧ ɤ ɭ ɪ 301
ɹ ɣ ɫ ɬ 301
ɪ ɳ     475
ͳ ͽ     475
ʈ ʂ     565
ˈ ϡ     565
出现问题时,
locale
的输出:

$ locale 
LANG=en_US.UTF-8                                                                                                                                                                                               
LC_CTYPE="en_US.UTF-8"                                                                                                                                                                                         
LC_NUMERIC=de_DE.UTF-8                                                                                                                                                                                         
LC_TIME=de_DE.UTF-8                                                                                                                                                                                            
LC_COLLATE="en_US.UTF-8"                                                                                                                                                                                       
LC_MONETARY=de_DE.UTF-8                                                                                                                                                                                        
LC_MESSAGES="en_US.UTF-8"                                                                                                                                                                                      
LC_PAPER=de_DE.UTF-8                                                                                                                                                                                           
LC_NAME=de_DE.UTF-8                                                                                                                                                                                            
LC_ADDRESS=de_DE.UTF-8                                                                                                                                                                                         
LC_TELEPHONE=de_DE.UTF-8                                                                                                                                                                                       
LC_MEASUREMENT=de_DE.UTF-8                                                                                                                                                                                     
LC_IDENTIFICATION=de_DE.UTF-8                                                                                                                                                                                  
LC_ALL=                   
编辑:归一化后使用:

cat duplicates | uconv -f utf8 -t utf8 -x nfc > duplicates.nfc
我仍然得到同样的结果

编辑:根据
iconv
-(from),文件是有效的UTF-8

编辑:看起来与此类似: 和

它正在FreeBSD上运行

这可能是由于。Unicode中有一些代码点序列是不同的,但被认为是等价的

一个简单的例子就是。许多重音字符(如“é”)可以表示为单个代码点(U+00E9,带锐音符的拉丁小写字母E),也可以表示为不可接受字符和组合字符的组合,例如两个字符序列(拉丁小写字母E,组合锐音符)

这两个字节序列明显不同,因此在C语言环境中,它们的比较是不同的。但在UTF-8语言环境中,由于Unicode规范化,它们被视为相同的

下面是一个简单的两行文件,其中包含以下示例:

$ echo -e '\xc3\xa9\ne\xcc\x81' > test.txt
$ cat test.txt
é
é
$ hexdump -C test.txt
00000000  c3 a9 0a 65 cc 81 0a                              |...e...|
00000007
$ LC_ALL=C uniq -d test.txt  # No output
$ LC_ALL=en_US.UTF-8 uniq -d test.txt
é

由n.m.编辑。并非所有Linux系统都进行Unicode规范化。

在这一点上纯粹是猜测,因为我们看不到实际数据,但我猜类似的情况正在发生

UTF-8将代码点0-127编码为其代表字节值。上面的值占用两个或更多字节。有一个规范定义,其中的值范围使用一定数量的字节,以及这些字节的格式。然而,代码点可以通过多种方式进行编码。例如,ASCII空间-32可以编码为0x20(其规范编码),但也可以编码为0xc0a0。这违反了对编码的严格解释,因此格式良好的UTF-8编写应用程序永远不会以这种方式编码。然而,通常编写解码器是为了更宽容,以处理错误编码,因此在您的特定情况下,UTF-8解码器可能会看到一个序列不是严格一致的编码码点,并以最合理的方式对其进行解释,这将使它看到某些多字节序列与其他序列相同。区域设置排序序列也会产生进一步的效果


在C语言环境中,0x20肯定会在0xc0之前排序,但在UTF-8中,如果它获取后面的0xa0,则该单个字节将被视为等于两个字节,因此将一起排序。

我将问题归结为
strcoll()
函数的问题,该函数与Unicode规范化无关。概述:我举了一个最小的例子,演示了
uniq
的不同行为,具体取决于当前的语言环境:

$ echo -e "\xc9\xa2\n\xc9\xac" > test.txt
$ cat test.txt
ɢ
ɬ
$ LC_COLLATE=C uniq -D test.txt
$ LC_COLLATE=en_US.UTF-8 uniq -D test.txt
ɢ
ɬ
显然,如果语言环境是
en_US,UTF-8
uniq
会将
ɢ
ɬ
视为重复的,但事实并非如此。然后,我使用
valgrind
再次运行相同的命令,并使用
kcachegrind
研究两个调用图

$ LC_COLLATE=C valgrind --tool=callgrind uniq -D test.txt
$ LC_COLLATE=en_US.UTF-8 valgrind --tool=callgrind uniq -D test.txt
$ kcachegrind callgrind.out.5754 &
$ kcachegrind callgrind.out.5763 &
唯一的区别是,带有
LC\u-COLLATE=en\u-US.UTF-8
的版本称为
strcoll()
,而
LC\u-COLLATE=C
的版本则没有。因此,我在
strcoll()
上给出了以下最简单的示例:


那么,这里怎么了?为什么
strcoll()
为两个不同的字符返回
0
(相等)?

如果提供一个小文件,其中包含显示不同行为的实际行,可能会有所帮助。删除行,直到得到一对在en_US语言环境中而不是在C语言环境中重复的行。我正在研究它。首先,看看它是否发生在前10000行左右(使用
head
命令),或者可能是对uniq输出的其中一行中出现的字符进行grep。uniq输出没有帮助,因为它没有显示它认为等效的两个不同的C语言环境行的实际内容。真的,你应该能够减少工作量,直到你有一个恰好由两行组成的文件,而en_-US UTF-8语言环境认为是重复的,而C则不是。@ninjalj:“应该将
ae
视为平等吗?
SS
ue
?”显然取决于当前的区域设置。但是
ß
א
呢<代码>θ
<代码>∫
不确定我是否犯了错误,但这在这里似乎不起作用:我已经使用:
cat duplicates | uconv-f utf8-t utf8-x nfc>duplicates.nfc
对数据进行了规范化,并且仍然会根据locale@mt_:嗯,那么还有别的事情发生了。FWIW我无法重现您发布的测试数据的问题。无论
LC\u-COLLATE
LC\u-ALL
是什么,对我来说
uniq-d
的输出总是空的。问题出在Ubuntu 12.04机器上。我刚刚下载了这个文件,可以在Ubuntu 13.10机器上重现重复的问题。@AdamRosenfield:
LC_ALL=en_US.UTF-8 uniq-d test.txt
在我的机器上没有输出任何东西,有什么用?我刚刚在CentOS 6.4机器上测试了它,得到了同样的结果
$ LC_COLLATE=C valgrind --tool=callgrind uniq -D test.txt
$ LC_COLLATE=en_US.UTF-8 valgrind --tool=callgrind uniq -D test.txt
$ kcachegrind callgrind.out.5754 &
$ kcachegrind callgrind.out.5763 &
#include <iostream>
#include <cstring>
#include <clocale>

int main()
{
    const char* s1 = "\xc9\xa2";
    const char* s2 = "\xc9\xac";
    std::cout << s1 << std::endl;
    std::cout << s2 << std::endl;

    std::setlocale(LC_COLLATE, "en_US.UTF-8");
    std::cout << std::strcoll(s1, s2) << std::endl;
    std::cout << std::strcmp(s1, s2) << std::endl;

    std::setlocale(LC_COLLATE, "C");
    std::cout << std::strcoll(s1, s2) << std::endl;
    std::cout << std::strcmp(s1, s2) << std::endl;

    std::cout << std::endl;

    s1 = "\xa2";
    s2 = "\xac";
    std::cout << s1 << std::endl;
    std::cout << s2 << std::endl;

    std::setlocale(LC_COLLATE, "en_US.UTF-8");
    std::cout << std::strcoll(s1, s2) << std::endl;
    std::cout << std::strcmp(s1, s2) << std::endl;

    std::setlocale(LC_COLLATE, "C");
    std::cout << std::strcoll(s1, s2) << std::endl;
    std::cout << std::strcmp(s1, s2) << std::endl;
}
ɢ
ɬ
0
-1
-10
-1

�
�
0
-1
-10
-1