關(guān)于Bash shell在不同locale下的”異常”表現(xiàn)之探討
peakflys原創(chuàng)作品,轉(zhuǎn)載請注明原作者和源鏈接現(xiàn)有一個文本test.xml,內(nèi)容大致如下:
<Word content="㈠"/>
<Word content="㈢"/>
<Word content="㈣"/>
<Word content="㈤"/>
<Word content="㈥"/>
<Word content="㈦"/>
<Word content="㈧"/>
<Word content="㈨"/>
<Word content="㈩"/>
<Word content="㊣"/>
<Word content="GM"/>
<Word content="GM"/>
<Word content="sex"/>
<Word content="G M"/>
<Word content="fuck"/>
<Word content="shit"/>
文本中顯而易見含有重復(fù)行,假如讓你來去重的話,你可能會說so easy,直接手動去掉多出來的那項(xiàng)GM就行了。但是如果這個文本有上萬行,而且上下文內(nèi)容沒有這么明顯的相似性歸類的話,又要怎么辦?
在windows下相信大家很容易寫出一個小程序來處理這樣的文本,而在linux下工作的人肯定馬上會想到諸如sort、uniq、awk等命令,這種問題一條指令就搞定了嘛:
但是結(jié)果卻大跌眼鏡,輸出如下:
<Word content="㈠"/>
<Word content="GM"/>
<Word content="sex"/>
<Word content="fuck"/>
因?yàn)槔觮est.xml中內(nèi)容只有十幾行,很明顯可以看出來去重后的結(jié)果不對!
那么使用uniq 呢?輸出結(jié)果同上……
那使用文本處理神器awk呢?輸出如下:
<Word content="㈠"/>
<Word content="㈢"/>
<Word content="㈣"/>
<Word content="㈤"/>
<Word content="㈥"/>
<Word content="㈦"/>
<Word content="㈧"/>
<Word content="㈨"/>
<Word content="㈩"/>
<Word content="㊣"/>
<Word content="GM"/>
<Word content="sex"/>
<Word content="G M"/>
<Word content="fuck"/>
<Word content="shit"/>
神器就是神器!這個結(jié)果才是我們需要的嘛。
但是sort和uniq為什么篩選出的結(jié)果有問題呢?
估計(jì)大家通過文章的標(biāo)題早就知道原因了,廢話不多說,直接看一下sort 的manual吧。里面有這么一句:
先看一下我本地的LANG設(shè)置:
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
依照文檔中說明重新設(shè)置一下本地locale的環(huán)境變量:LANG=C
之后使用sort或者uniq命令輸出正確。
Linux下有不少命令的輸出是和本地的locale設(shè)置有關(guān)的,除sort和uniq外,還有l(wèi)s等,在此不再擴(kuò)展展開。
還如往常一樣,上面講述的僅僅是一個結(jié)果,以及淺層次的原因,為了對得起題目中的”探討”兩個字,咱們繼續(xù)細(xì)究一下sort的實(shí)現(xiàn),以及發(fā)生這種情況的具體原因。
其實(shí)sort的實(shí)現(xiàn)代碼很精致也很高效,有興趣的同學(xué)可以自行察看,下面僅摘錄一些和本篇文章相關(guān)的代碼。
首先是一些初始化代碼:
set_program_name (argv[0]);
setlocale (LC_ALL, ""); //peakflys引manual注: If locale is "", each part of the locale that should be modified is set according to the environment variables.
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
initialize_exit_failure (SORT_FAILURE);
hard_LC_COLLATE = hard_locale (LC_COLLATE);
setlocale上面已經(jīng)注釋了,第二個參數(shù)為空字符時取本機(jī)的locale設(shè)置,hard_locale的實(shí)現(xiàn)代碼就不貼了,它主要是通過查找locale中LC_COLLATE有沒有"C"或者"POSIX"。
有的話置標(biāo)記hard_LC_COLLATE為false,否則為true。這個標(biāo)志位很重要,具體使用見下文。
下面給出sort實(shí)現(xiàn)的核心比較操作的代碼:
{
int diff;
size_t alen, blen;
/* First try to compare on the specified keys (if any).
The only two cases with no key at all are unadorned sort,
{
diff = keycompare (a, b);
if (diff || unique || stable)
return diff;
}
/* If the keys all compare equal (or no keys were specified)
fall through to the default comparison. */
if (alen == 0)
diff = - NONZERO (blen);
else if (blen == 0)
diff = 1;
else if (hard_LC_COLLATE) //peakflys注:上文提到的這個標(biāo)記位!
{
/* Note xmemcoll0 is a performance enhancement as
it will not unconditionally write '\0' after the
}
else if (! (diff = memcmp (a->text, b->text, MIN (alen, blen)))) //peakflys注:設(shè)置LANG=C后,程序邏輯在這里?
diff = alen < blen ? -1 : alen != blen;
return reverse ? -diff : diff;
}
上面代碼注釋很清楚相信大家已經(jīng)知道為什么設(shè)置LANG=C (linux系統(tǒng)中"C"和"POSIX"在locale意義上等價)后,sort操作就正常了。
那么為什么使用UTF-8的locale時會出問題呢?
那我們繼續(xù)看xmemcoll0相關(guān)的實(shí)現(xiàn):
xmemcoll0 (char const *s1, size_t s1size, char const *s2, size_t s2size)
{
int diff = memcoll0 (s1, s1size, s2, s2size);
int collation_errno = errno;
if (collation_errno)
collate_error (collation_errno, s1, s1size - 1, s2, s2size - 1);
return diff;
}
int
memcoll0 (char const *s1, size_t s1size, char const *s2, size_t s2size)
{
if (s1size == s2size && memcmp (s1, s2, s1size) == 0)
{
errno = 0;
return 0;
}
else
return strcoll_loop (s1, s1size, s2, s2size);
}
static int
strcoll_loop (char const *s1, size_t s1size, char const *s2, size_t s2size)
{
int diff;
while (! (errno = 0, (diff = strcoll (s1, s2)) || errno))
{
/* strcoll found no difference, but perhaps it was fooled by NUL
characters in the data. Work around this problem by advancing
size_t size2 = strlen (s2) + 1;
s1 += size1;
s2 += size2;
s1size -= size1;
s2size -= size2;
if (s1size == 0)
return - (s2size != 0);
if (s2size == 0)
return 1;
}
return diff;
}
至此我們可以看到sort關(guān)于locale的比較實(shí)現(xiàn)最終是通過庫函數(shù)strcoll來實(shí)現(xiàn)的(礙于篇幅,這個glibc庫函數(shù)的實(shí)現(xiàn)代碼我就不貼了,感興趣的可以自行下載研究),這個函數(shù)通過調(diào)用__strcoll_l來根據(jù)不同locale定義的weights等信息來比較兩個字符串。
其他的命令,諸如uniq、ls等的情況和sort命令大致一樣,大家可以自己down下GNU的
眼尖的同學(xué)可能會發(fā)現(xiàn)上面例子中文本就是大天朝下游戲臟詞庫中常見的一部分,而這些內(nèi)容時常需要根據(jù)當(dāng)前情況追加一些新的紀(jì)錄,所以如果沒有大天朝的奇葩制度和游戲玩家的“無窮智慧”也就沒有文中開場例子的使用場景。
講了那么多遍locale,可能有些人要問,什么是locale?
其實(shí)locale就是根據(jù)計(jì)算機(jī)用戶所使用的語言,所在國家或者地區(qū),以及當(dāng)?shù)氐奈幕瘋鹘y(tǒng)所定義的一個軟件運(yùn)行時的語言環(huán)境。狹隘的說,它規(guī)定了字符集,以及這種字符集具體的展示方式。 bash shell之所以很多命令同locale相關(guān),也是基于不同地區(qū)、不同語言的用戶可以通過選擇各自的locale來達(dá)到定制化的效果。
更具體的介紹就不在這里討論了,感興趣的朋友可以通過下面鏈接了解:
http://m.shnenglu.com/peakflys/articles/209773.html
by peakflys 08:51:19 Tuesday, February 10, 2015
posted on 2015-02-10 09:10 peakflys 閱讀(1807) 評論(0) 編輯 收藏 引用 所屬分類: 操作系統(tǒng) 、雜談