locales如何在Linux/POSIX中工作以及应用了哪些转换?

mt_*_*mt_ 11 linux locale posix utf-8 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-r-00000 | uniq -d 
$ # no duplicates found
Run Code Online (Sandbox Code Playgroud)

运行使用读取文件的自定义C++程序时也会出现重复项std::stringstream- 由于使用en_US.UTF-8区域设置时出现重复,它会失败. C++似乎至少对于std::string输入/输出没有影响.

为什么在使用UTF-8语言环境时找到重复项,并且在C语言环境中找不到重复项?

导致此行为的文本的区域设置有哪些转换?

编辑:是一个小例子

$ uniq -D duplicates.small.nfc 
? ? ? ? ?       224
? ? ? ? ?       224
? ? ? ? 251
? ? ? ? 251
? ? ? ? ?       291
? ? ? ? ?       291
? ? ? ? 301
? ? ? ? 301
? ? ? ? 301
? ?     475
? ?     475
? ?     565
? ?     565
Run Code Online (Sandbox Code Playgroud)

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

编辑:使用以下规范化后:

cat duplicates | uconv -f utf8 -t utf8 -x nfc > duplicates.nfc
Run Code Online (Sandbox Code Playgroud)

我仍然得到相同的结果

编辑:文件是有效的UTF-8根据iconv- (从这里)

$ iconv -f UTF-8 duplicates -o /dev/null
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)

编辑:看起来像这样的东西:http://xahlee.info/comp/unix_uniq_unicode_bug.htmlhttps://lists.gnu.org/archive/html/bug-coreutils/2012-07/msg00072.html

它正在使用FreeBSD

Mar*_*ann 7

我已经将问题归结为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
?
?
Run Code Online (Sandbox Code Playgroud)

显然,如果区域设置是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 &
Run Code Online (Sandbox Code Playgroud)

唯一的区别是,与该版本LC_COLLATE=en_US.UTF-8名为strcoll()LC_COLLATE=C没有这样做.所以我提出了以下最小例子strcoll():

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

?
?
0
-1
-10
-1

?
?
0
-1
-10
-1
Run Code Online (Sandbox Code Playgroud)

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

  • `strcoll()`不只是比较字符:它比较字符的排序.由于英语中没有为您使用的字符定义排序(校对)顺序,因此它们在同一个地方`strcoll()`(即它们在`strcoll()`函数形成的顺序中处于同一位置). (2认同)