Linux的sort如何對字串進行排序

介紹

這一切都始於一個簡短的腳本,該腳本應該結合地址訊息 電子郵件 員工從郵件清單使用者清單中獲取,員工職位從HR部門資料庫中獲取。兩個列表均匯出為 Unicode 文字文件 UTF-8的 並以 Unix 行結尾儲存。

內容 郵件.txt

Иванов Андрей;[email protected]

內容 buhg.txt

Иванова Алла;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Абаканов Михаил;маляр

為了合併,檔案透過 Unix 指令進行排序 分類 並提交給Unix程式的輸入 加入,意外失敗並出現錯誤:

$> sort buhg.txt > buhg.srt
$> sort mail.txt > mail.srt
$> join buhg.srt mail.srt > result
join: buhg.srt:4: is not sorted: Иванов Андрей;слесарь

肉眼觀察排序結果,整體來說排序是正確的,但在男女姓氏重合的情況下,女性排在男性之前:

$> sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванова Алла;маляр
Иванов Андрей;слесарь

看起來像是 Unicode 中的排序故障,或是排序演算法中女性主義的體現。當然,第一種說法更合理。

我們暫時把它推遲吧 加入 並專注於 分類。讓我們試著用科學的戳戳來解決這個問題。首先,讓我們更改區域設置 EN_USru_RU。要排序,設定環境變數就足夠了 LC_COLLATE,但我們不會在瑣事上浪費時間:

$> LANG=ru_RU.UTF-8 sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванова Алла;маляр
Иванов Андрей;слесарь

沒有改變。

讓我們嘗試將檔案重新編碼為單字節編碼:

$> iconv -f UTF-8 -t KOI8-R buhg.txt 
 | LANG=ru_RU.KOI8-R sort 
 | iconv -f KOI8-R -t UTF8

一切都沒有改變。

你無能為力,只能在網路上尋找解決方案。沒有任何直接與俄羅斯姓氏相關的內容,但存在關於其他排序奇怪現象的問題。例如,這裡有一個問題: unix 排序將「-」(破折號)字元視為不可見。簡而言之,字串「a-b」、「aa」、「ac」被排序為「aa」、「a-b」、「ac」。

答案在任何地方都是標準的:使用程式設計師區域設置 “C” 你會很高興。咱們試試:

$> LANG=C sort buhg.txt
Ёлкина Элла;крановщица
Абаканов Михаил;маляр
Иванов Андрей;слесарь
Иванова Алла;адвокат

有些事情發生了變化。伊凡諾夫一家按照正確的順序排列,儘管約基納在某個地方滑倒了。讓我們回到最初的問題:

$> LANG=C sort buhg.txt > buhg.srt
$> LANG=C sort mail.txt > mail.srt
$> LANG=C join buhg.srt mail.srt > result

正如互聯網所承諾的那樣,它沒有錯誤地運行。儘管約基娜在第一線,但情況仍然如此。

問題似乎解決了,但為了以防萬一,讓我們嘗試另一種俄語編碼 - Windows CP1251:

$> iconv -f UTF-8 -t CP1251 buhg.txt 
 | LANG=ru_RU.CP1251 sort 
 | iconv -f CP1251 -t UTF8 

奇怪的是,排序結果將與區域設定一致 “C”,因此整個範例運行沒有錯誤。某種神秘主義。

我不喜歡程式設計中的神秘主義,因為它通常會掩蓋錯誤。我們必須認真研究它是如何運作的。 分類 它有什麼影響? LC_COLLATE .

最後我會嘗試回答以下問題:

  • 為什麼女性姓氏排序不正確?
  • 為什麼 LANG=ru_RU.CP1251 結果是等價的 郎=C
  • 為什麼 分類 и 加入 關於排序字串順序的不同想法
  • 為什麼我所有的例子都有錯?
  • 最後如何根據您的喜好對字串進行排序

以 Unicode 排序

第一站將是第10號技術報告,題為 Unicode 排序規則演算法 在線 unicode.org。該報告包含許多技術細節,所以讓我簡要總結一下主要想法。

整理 —「比較」字串是任何排序演算法的基礎。演算法本身可能有所不同(「冒泡」、「合併」、「快速」),但它們都會使用一對字串的比較來確定它們出現的順序。

用自然語言對字串進行排序是一個相當複雜的問題。即使在最簡單的單字節編碼中,字母表中的字母順序(即使在某種程度上與英語拉丁字母表不同)也將不再與編碼這些字母的數值順序一致。所以在德文字母表中這個字母 Ö 站在之間 О и P,並且在編碼中 CP850 她介於 ÿ и Ü.

您可以嘗試從特定編碼中抽象化出來,並考慮按某種順序排列的「理想」字母,就像 Unicode 中所做的那樣。編碼 UTF8編碼, UTF16編碼 或一位元組 KOI8-R (如果需要 Unicode 的有限子集)將給出字母的不同數字表示,但引用基底表的相同元素。

事實證明,即使我們從頭開始建立符號表,我們也無法為其分配通用符號順序。在使用相同字母的不同國家字母表中,這些字母的順序可能不同。例如,在法語中 Æ 將被視為連字並按字串排序 AE。挪威語 Æ 將是一個單獨的字母,位於 Z。順便說一下,除了像這樣的連字 Æ 有用幾個符號寫的字母。所以在捷克語字母表中有一個字母 Ch,介於 H и I.

除了字母差異之外,還有其他民族傳統也會影響排序。特別是,出現了一個問題:由大寫字母和小寫字母組成的單字在字典中應該以什麼順序出現?標點符號的使用也可能影響排序。在西班牙語中,疑問句的開頭使用倒問號(你喜歡音樂嗎?)。在這種情況下,很明顯疑問句不應該被分組到字母表之外的單獨簇中,但是如何對帶有其他標點符號的行進行排序呢?

我不會詳細討論用與歐洲語言截然不同的語言對字串進行排序。請注意,在從右到左或從上到下書寫方向的語言中,行中的字符很可能按閱讀順序存儲,甚至非字母書寫系統也有自己的逐字符排序行的方式。例如,象形文字可以按樣式排序(漢字按鍵) 或按發音。老實說,我不知道表情符號應該如何排列,但你也可以為它們想出一些東西。

基於上面列出的特徵,制定了基於Unicode表比較字串的基本要求:

  • 字串的比較不依賴字元在碼表中的位置;
  • 形成單一字元的字元序列被簡化為規範形式(A + 頂部的圓圈與 Å);
  • 比較字串時,會在字串的上下文中考慮一個字符,並在必要時將其與其鄰居組合成一個比較單元(Ch 捷克語)或分為幾個(Æ 法語);
  • 所有國家特徵(字母、大寫/小寫、標點符號、書寫順序類型)必須按照手動分配的順序(表情符號)進行配置;
  • 比較不僅對於排序很重要,而且在許多其他地方也很重要,例如指定行範圍(將 {A... z} 替換為 打壞);
  • 比較應該相當快完成。

此外,該報告的作者制定了演算法開發人員不應依賴的比較屬性:

  • 比較演算法不應要求每種語言都有單獨的字元集(俄語和烏克蘭語共享大多數西里爾字母字元);
  • 比較不應依賴 Unicode 表中的字元順序;
  • 字串權重不應該是字串的屬性,因為不同文化背景中的同一字串可能有不同的權重;
  • 合併或拆分時行權重可能會發生變化(從 x < y 它不遵循那個 xz < yz);
  • 從排序演算法的角度來看,具有相同權重的不同字串被認為是相等的。引入此類字串的額外排序是可能的,但可能會降低效能;
  • 在重複排序過程中,具有相同權重的行可能會被交換。魯棒性是特定排序演算法的屬性,而不是字串比較演算法的屬性(參見上一段);
  • 隨著文化傳統的完善/變化,排序規則可能會隨著時間而改變。

也規定比較演算法對正在處理的字串的語意一無所知。因此,僅由數字組成的字串不應與數字進行比較,並且在英文名稱列表中,文章 (披頭士樂隊).

為了滿足所有指定的要求,提出了一種多層(實際上是四級)表排序演算法。

以前,字串中的字元被簡化為規範形式並分組為比較單元。每個比較單元都被分配了與多個比較等級相對應的多個權重。比較單元的權重是可以進行或多或少比較的有序集合的元素(在本例中為整數)。特殊意義 忽略 (0x0)表示在相應的比較層級該單元不參與比較。使用相應層級的權重,可以將字串的比較重複多次。在每一級,依序比較兩行比較單元的權重。

在不同民族傳統的演算法的不同實作中,係數的值可能會有所不同,但Unicode標準包含一個基本的權重表 - “預設 Unicode 排序規則元素表” (杜克大學)。我想指出設定變數 LC_COLLATE 實際上是字串比較函數中權重表選擇的指示。

加權係數 杜克大學 安排如下:

  • 在第一級,所有字母都被簡化為相同的大小寫,變音符號被丟棄,標點符號(並非全部)被忽略;
  • 在第二級,僅考慮變音符號;
  • 在第三級,僅考慮情況;
  • 在第四級,僅考慮標點符號。

比較分多次進行:首先,比較第一級的係數;若權重一致,則與第二級權重重複比較;然後也許是第三個和第四個。

當行包含具有不同權重的匹配比較單位時,比較結束。在所有四個層級上具有相同權重的行被視為彼此相等。

這個演算法(帶有一堆額外的技術細節)給了第 10 號報告的名稱 - 《Unicode 校對演算法》 (加州大學洛杉磯分校).

這是我們範例中的排序行為變得更加清晰的地方。最好將它與 Unicode 標準進行比較。

測試實施 加州大學洛杉磯分校 有一個特殊的 測試, 使用 權重文件,實施 杜克大學。您可以在秤文件中找到各種有趣的東西。例如麻將、歐式骨牌的順序,以及一副牌中花色的順序(符號 1F000 並進一步)。紙牌花色依照橋牌-PCBT的規則放置,花色中的牌的順序為T、2,3、XNUMX...K。

手動檢查行是否正確排序 杜克大學 這將是相當乏味的,但是,對我們來說幸運的是,有一個用於使用 Unicode 的庫的示例性實現 - ”Unicode 的國際元件“(重症監護室).

在該圖書館的網站上,開發於 IBM,有演示頁面,包括 字串比較演算法頁面。我們使用預設設定輸入測試線,你瞧,我們得到了完美的俄語排序。

Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Иванова Алла;адвокат

順便說一句,在網站上 重症監護室 您可以在處理標點符號時找到比較演算法的說明。在範例中 整理常見問題 撇號和連字號將被忽略。

Unicode 幫助了我們,但要尋找奇怪行為的原因 分類 в Linux 將不得不去其他地方。

glibc中的排序

快速查看實用程式原始碼 分類GNU 核心實用程式 表明在實用程式本身中,本地化歸結為列印變數的當前值 LC_COLLATE 在調試模式下運行時:

$ sort --debug buhg.txt > buhg.srt
sort: using ‘en_US.UTF8’ sorting rules

使用標準函數執行字串比較 斯特科爾,這意味著所有有趣的東西都在圖書館裡 glibc的.

維基 該項目 glibc的 專用於字串比較 一段。從這段可以看出,在 glibc的 排序基於我們已知的演算法 加州大學洛杉磯分校 (Unicode 排序規則演算法)和/或接近它的標準 品質政策 ISO 14651 (國際字串排序和比較)。關於最新標準,需要注意的是網站上 標準.iso.org 品質政策 ISO 14651 官方聲明公開可用,但相應的連結指向不存在的頁面。谷歌返回了幾個頁面,其中包含指向官方網站的鏈接,這些網站提供以一百歐元購買標準電子版的鏈接,但在搜索結果的第三頁或第四頁上也有直接鏈接 PDF。一般來說,該標準實際上與 加州大學洛杉磯分校,但是讀起來比較枯燥,因為它沒有包含字串排序的國家特徵的清晰範例。

最有趣的訊息 維基 有一個連結到 錯誤追蹤器 討論了字串比較的實現 glibc的。從討論中可以得知 glibc的 用於比較字串 的ISO個人餐桌 通用模板表 (CTT),其地址可在應用程式中找到 A 標準 品質政策 ISO 14651。 2000 年至 2015 年間,該表 glibc的 沒有維護者,並且與當前版本的標準有很大不同(至少在外部)。從2015年到2018年,對新版本的表進行了適應,現在您有機會在現實生活中遇到新版本的表(CentOS 8的)和舊的(CentOS 7的).

現在我們已經掌握了有關演算法和輔助表的所有信息,我們可以回到原來的問題並了解如何在俄語語言環境中正確對字串進行排序。

ISO 14651 / 14652

我們感興趣的表的源代碼 CTT 在大多數發行版上 Linux 在目錄中 /usr/share/i18n/locales/。表本身在文件中 iso14651_t1_common。這就是檔案指令 複製 iso14651_t1_common 包含在文件中 ISO14651_t1,進而包含在國家檔案中,包括 EN_US и ru_RU。在大多數發行版上 Linux 所有來源檔案都包含在基本安裝中,但如果它們不存在,您將必須安裝發行版中的附加套件。

文件結構 ISO14651_t1 可能看起來非常冗長,並且構造名稱的規則並不明顯,但如果你仔細看一下,一切都非常簡單。標準中描述了該結構 品質政策 ISO 14652,可以從網站下載一份副本 open-std.org。可以讀取文件格式的另一個描述 規格 POSIX開放組。作為閱讀標準的替代方法,您可以研究該函數的原始程式碼 校對讀 в glibc/locale/programs/ld-collat​​e.c.

文件結構如下圖所示:

預設情況下,該字符用作轉義字符,#字符後面的行尾為註釋。這兩個符號都可以重新定義,這是在新版本的表中所做的:

escape_char /
comment_char %

該文件將包含以下格式的令牌 (在哪裡 x - 十六進制數字)。這是編碼中 Unicode 代碼點的十六進位表示 UCS-4 (UTF-32的)。尖括號中的所有其他元素(包括 , 等等)被認為是簡單的字串常數,在上下文之外沒有什麼意義。

弦樂 LC_COLLATE 告訴我們接下來開始描述字串比較的資料。

首先,指定比較表中權重的名稱和符號組合的名稱。一般來說,兩種類型的名稱屬於兩個不同的實體,但在實際文件中它們是混合的。權重的名稱由關鍵字指定 整理符號 (比較字元)因為在比較時,具有相同重量的 Unicode 字元將被視為等效字元。

目前文件修訂版中該部分的總長度約為 900 行。我從幾個地方舉了例子來展示名稱和幾種語法類型的任意性。

LC_COLLATE

collating-symbol <RES-1>
collating-symbol <BLK>
collating-symbol <MIN>
collating-symbol <WIDE>
...
collating-symbol <ARABIC>
collating-symbol <ETHPC>
collating-symbol <OSMANYA>
...
collating-symbol <S1D000>..<S1D35F>
collating-symbol <SFFFF> % Guaranteed largest symbol value. Keep at end of this list
...
collating-element <U0413_0301> from "<U0413><U0301>"
collating-element <U0413_0341> from "<U0413><U0341>"

  • 整理符號 記錄一個字串 奧斯曼尼亞 在音階名稱表中
  • 整理符號 .. 註冊由前綴組成的名稱序列 S 和十六進制數字後綴 1D0001天35樓.
  • FFFF в 整理符號 看起來像一個很大的十六進制無符號整數,但是 這只是一個可能看起來像的名字
  • 產品名稱 表示編碼中的代碼點 UCS-4
  • 來自「」的整理元素 為一對 Unicode 點註冊一個新名稱。

一旦定義了權重的名稱,就指定了實際的權重。由於只有大於小於的關係在比較中才重要,因此權重由簡單的清單名稱序列決定。首先列出「較輕」的重量,然後列出「較重」的重量。讓我提醒您,每個 Unicode 字元都分配有四種不同的權重。在這裡,它們被組合成一個有序序列。理論上,任何符號名稱都可以在四個層級中的任何一個層級使用,但評論表明開發人員在心裡將名稱分為不同層級。

% Symbolic weight assignments

% Third-level weight assignments
<RES-1>
<BLK>
<MIN>
<WIDE>
...
% Second-level weight assignments
<BASE>
<LOWLINE> % COMBINING LOW LINE
<PSILI> % COMBINING COMMA ABOVE
<DASIA> % COMBINING REVERSED COMMA ABOVE
...
% First-level weight assignments
<S0009> % HORIZONTAL TABULATION 
<S000A> % LINE FEED
<S000B> % VERTICAL TABULATION
...
<S0434> % CYRILLIC SMALL LETTER DE
<S0501> % CYRILLIC SMALL LETTER KOMI DE
<S0452> % CYRILLIC SMALL LETTER DJE
<S0503> % CYRILLIC SMALL LETTER KOMI DJE
<S0453> % CYRILLIC SMALL LETTER GJE
<S0499> % CYRILLIC SMALL LETTER ZE WITH DESCENDER
<S0435> % CYRILLIC SMALL LETTER IE
<S04D7> % CYRILLIC SMALL LETTER IE WITH BREVE
<S0454> % CYRILLIC SMALL LETTER UKRAINIAN IE
<S0436> % CYRILLIC SMALL LETTER ZHE

最後是實際重量表。

權重部分包含在關鍵字行中 訂單開始時間 и 訂單結束。額外選項 訂單開始時間 確定在每個比較層級掃描行的方向。預設是 前鋒。該部分的主體由包含符號代碼及其四個權重的行組成。字元代碼可以由字元本身、代碼點或先前定義的符號名稱來表示。權重也可以賦予符號名稱、代號點或符號本身。如果使用代碼點或字符,則它們的權重與代碼點的數值(Unicode 表中的位置)相同。未明確指定的字元(據我所知)被視為分配給表,其主要權重與 Unicode 表中的位置相符。特殊重量值 忽視 意味著該符號在適當的比較層級被忽略。

為了示範音階的結構,我選擇了三個相當明顯的片段:

  • 完全被忽略的字符
  • 前兩級相當於數字三的符號
  • 西里爾字母的開頭,不包含變音符號,因此主要按第一級和第三級排序。

order_start forward;forward;forward;forward,position
<U0000> IGNORE;IGNORE;IGNORE;IGNORE % NULL (in 6429)
<U0001> IGNORE;IGNORE;IGNORE;IGNORE % START OF HEADING (in 6429)
<U0002> IGNORE;IGNORE;IGNORE;IGNORE % START OF TEXT (in 6429)
...
<U0033> <S0033>;<BASE>;<MIN>;<U0033> % DIGIT THREE
<UFF13> <S0033>;<BASE>;<WIDE>;<UFF13> % FULLWIDTH DIGIT THREE
<U2476> <S0033>;<BASE>;<COMPAT>;<U2476> % PARENTHESIZED DIGIT THREE
<U248A> <S0033>;<BASE>;<COMPAT>;<U248A> % DIGIT THREE FULL STOP
<U1D7D1> <S0033>;<BASE>;<FONT>;<U1D7D1> % MATHEMATICAL BOLD DIGIT THREE
...
<U0430> <S0430>;<BASE>;<MIN>;<U0430> % CYRILLIC SMALL LETTER A
<U0410> <S0430>;<BASE>;<CAP>;<U0410> % CYRILLIC CAPITAL LETTER A
<U04D1> <S04D1>;<BASE>;<MIN>;<U04D1> % CYRILLIC SMALL LETTER A WITH BREVE
<U0430_0306> <S04D1>;<BASE>;<MIN>;<U04D1> % CYRILLIC SMALL LETTER A WITH BREVE
...
<U0431> <S0431>;<BASE>;<MIN>;<U0431> % CYRILLIC SMALL LETTER BE
<U0411> <S0431>;<BASE>;<CAP>;<U0411> % CYRILLIC CAPITAL LETTER BE
<U0432> <S0432>;<BASE>;<MIN>;<U0432> % CYRILLIC SMALL LETTER VE
<U0412> <S0432>;<BASE>;<CAP>;<U0412> % CYRILLIC CAPITAL LETTER VE
...
order_end

現在您可以返回到對本文開頭的範例進行排序。埋伏就在權重表的這一部分:

<U0020> IGNORE;IGNORE;IGNORE;<U0020> % SPACE
<U0021> IGNORE;IGNORE;IGNORE;<U0021> % EXCLAMATION MARK
<U0022> IGNORE;IGNORE;IGNORE;<U0022> % QUOTATION MARK
...

可以看出,該表中的標點符號來自表 ASCII碼 比較字串時(包括空格)幾乎總是被忽略。唯一的例外是除了在匹配位置找到的標點符號之外所有內容都匹配的行。我的範例中的比較演算法行(排序後)如下所示:

АбакановМихаилмаляр
ЁлкинаЭллакрановщица
ИвановаАлламаляр
ИвановАндрейслесарь

考慮到在音階表中,俄語中的大寫字母在小寫字母之後(在第三級 比...更重 ),排序看起來絕對正確。

設定變數時 LC_COLLATE=C 載入一個特殊的表來指定逐字節比較

static const uint32_t collseqwc[] =
{
  8, 1, 8, 0x0, 0xff,
  /* 1st-level table */
  6 * sizeof (uint32_t),
  /* 2nd-level table */
  7 * sizeof (uint32_t),
  /* 3rd-level table */
  L'x00', L'x01', L'x02', L'x03', L'x04', L'x05', L'x06', L'x07',
  L'x08', L'x09', L'x0a', L'x0b', L'x0c', L'x0d', L'x0e', L'x0f',

...
  L'xf8', L'xf9', L'xfa', L'xfb', L'xfc', L'xfd', L'xfe', L'xff'
};

由於在 Unicode 中代碼點 Ё 位於 A 之前,因此字串會相應地排序。

文字和二進位表

顯然,字串比較是一個極為常見的操作,而表解析 CTT 這是一個相當昂貴的過程。為了優化對錶的訪問,使用以下命令將其編譯為二進位形式 本地定義.

團隊 本地定義 接受具有國家特徵表的文件作為參數(選項 -i),其中所有字元均由 Unicode 點表示,以及 Unicode 點與特定編碼的字元之間的對應關係檔案(選項 -f)。這項工作的結果是,為區域設定建立了二進位文件,其名稱在最後一個參數中指定。

格里布 支援兩種二進位檔案格式:“傳統”和“現代”。

傳統格式意味著語言環境的名稱是子目錄的名稱 /usr/lib/語言環境/。此子目錄存放二進位文件 LC_COLLATE, LC_CTYPE, LC_TIME 等等。文件 LC_IDENTIFICATION 包含區域設定的正式名稱(可能與目錄名稱不同)和註釋。

現代格式涉及將所有語言環境儲存在一個檔案中 /usr/lib/locale/區域設定存檔,它被映射到所有進程的虛擬內存 glibc的。現代格式中的語言環境名稱受到一些規範化的影響 - 只有數字和字母縮減為小寫字母仍保留在編碼名稱中。所以 ru_RU.KOI8-R,將另存為 ru_RU.koi8r.

輸入檔在目前目錄以及目錄中搜尋 /usr/share/i18n/locales/ и /usr/share/i18n/charmaps/ 對於文件 CTT 和編碼文件,分別。

例如,命令

localedef -i ru_RU -f MAC-CYRILLIC ru_RU.MAC-CYRILLIC

將編譯文件 /usr/share/i18n/locales/ru_RU 使用編碼文件 /usr/share/i18n/charmaps/MAC-CYRILLIC.gz 並將結果保存在 /usr/lib/locale/區域設定存檔 以...之名 ru_RU.maccyrillic

如果您設定變數 LANG =的en_US.UTF-8 то glibc的 將在下列檔案和目錄序列中尋找語言環境二進位檔案:

/usr/lib/locale/locale-archive
/usr/lib/locale/en_US.UTF-8/
/usr/lib/locale/en_US/
/usr/lib/locale/enUTF-8/
/usr/lib/locale/en/

如果某個語言環境同時以傳統格式和現代格式出現,則優先考慮現代格式。

您可以使用以下命令查看編譯的語言環境列表 locale -a.

準備比較表

現在,有了這些知識,您就可以建立自己理想的字串比較表。表格應正確比較俄語字母,包括字母 Ё,同時根據表格考慮標點符號 ASCII碼.

準備自己的排序表的過程包括兩個階段:編輯權重表並使用命令將其編譯為二進位形式 本地定義.

為了以最小的編輯成本調整比較表,格式為 品質政策 ISO 14652 提供了調整現有表格權重的部分。該部分以關鍵字開頭 之後重新排序 並指示更換後的位置。該部分以該行結束 重新排序結束。如果需要修正表格的多個部分,則為每個這樣的部分建立一個部分。

我複製了文件的新版本 iso14651_t1_common и ru_RU 從儲存庫 glibc的 到我的主目錄 ~/.local/share/i18n/locales/ 並稍微編輯了該部分 LC_COLLATE в ru_RU。新版本的文件與我的版本完全相容 glibc的。如果您想使用舊版本的文件,則必須更改符號名稱以及表中替換開始的位置。

LC_COLLATE
% Copy the template from ISO/IEC 14651
copy "iso14651_t1"
reorder-after <U000D>
<U0020> <S0020>;<BASE>;<MIN>;<U0020> % SPACE
<U0021> <S0021>;<BASE>;<MIN>;<U0021> % EXCLAMATION MARK
<U0022> <S0022>;<BASE>;<MIN>;<U0022> % QUOTATION MARK
...
<U007D> <S007D>;<BASE>;<MIN>;<U007D> % RIGHT CURLY BRACKET
<U007E> <S007E>;<BASE>;<MIN>;<U007E> % TILDE
reorder-end
END LC_COLLATE

事實上,有必要更改其中的字段 LC_IDENTIFICATION 這樣他們就可以指向語言環境 ru_MY,但在我的示例中這不是必需的,因為我從語言環境搜尋中排除了存檔 區域設定存檔.

本地定義 透過變數處理我的資料夾中的文件 國際化路徑 您可以新增額外的目錄來搜尋輸入文件,並且可以將儲存二進位檔案的目錄指定為斜線的路徑:

$> I18NPATH=~/.local/share/i18n localedef -i ru_RU -f UTF-8 ~/.local/lib/locale/ru_MY.UTF-8

POSIX 假設在 您可以將絕對路徑寫入包含區域設定檔的目錄,以正斜線開頭,但是 glibc的 в Linux 所有路徑均從基底目錄開始計算,可透過變數覆蓋 定位路徑。安裝後 LOCPATH=~/.local/lib/locale/ 所有與本地化相關的文件將僅在我的資料夾中搜尋。具有變數集的語言環境存檔 定位路徑 被忽略。

這是決定性的測試:

$> LANG=ru_MY.UTF-8 LOCPATH=~/.local/lib/locale/ sort buhg.txt
Абаканов Михаил;маляр
Ёлкина Элла;крановщица
Иванов Андрей;слесарь
Иванова Алла;адвокат

萬歲!我們做到了!

有些錯誤

我已經回答了開頭提出的關於字串排序的問題,但是仍然有幾個關於錯誤的問題 - 可見的和不可見的。

讓我們回到最初的問題。

還有節目 分類 和程序 加入 使用相同的字串比較函數 glibc的。這是怎麼發生的 加入 在按命令排序的行上出現排序錯誤 分類 在語言環境中 面對en_US.UTF-8?答案很簡單: 分類 比較整個字串,並且 加入 僅比較鍵,預設情況下該鍵是字串的開頭到第一個空白字元。在我的範例中,這會導致錯誤訊息,因為行中第一個單字的排序與完整行的排序不符。

語言環境 “C” 保證在排序字串中,直到第一個空格的初始子字串也將被排序,但這只能掩蓋錯誤。可以選擇在沒有錯誤訊息的情況下給出不正確的文件合併結果的資料(具有相同姓氏但名字不同的人)。如果我們想要 加入 按全名合併檔案行,那麼正確的方法是明確指定欄位分隔符號並按鍵欄位排序,而不是按整行排序。在這種情況下,合併將正確進行,並且在任何語言環境中都不會出現錯誤:

$> sort -t ; -k 1 buhg.txt > buhg.srt
$> sort -t ; -k 1 mail.txt > mail.srt
$> join -t ; buhg.srt mail.srt > result

編碼中成功執行範例 CP1251 包含另一個錯誤。事實是,在我所知的所有發行版中 Linux 軟體包缺少已編譯的語言環境 ru_RU.CP1251。如果未找到編譯的語言環境,則 分類 默默地使用逐字節比較,這就是我們觀察到的。

順便說一句,還有另一個與編譯語言環境無法存取相關的小故障。團隊 LOCPATH=/tmp 語言環境 -a 將給出所有語言環境的列表 區域設定存檔,但是設定了變數 定位路徑 對於所有程序(包括最 當地) 這些區域設定將不可用。

$> LOCPATH=/tmp locale -a | grep en_US
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_COLLATE to default locale: No such file or directory
en_US
en_US.iso88591
en_US.iso885915
en_US.utf8

$> LC_COLLATE=en_US.UTF-8 sort --debug
sort: using ‘en_US.UTF-8’ sorting rules

$> LOCPATH=/tmp LC_COLLATE=en_US.UTF-8 sort --debug
sort: using simple byte comparison

結論

如果你是一個習慣於認為字串是一組位元組的程式設計師,那麼你的選擇 LC_COLLATE=C.

如果您是語言學家或字典編譯器,那麼您最好在您的語言環境中進行編譯。

如果您是一個簡單的用戶,那麼您只需要習慣該命令 ls -a 輸出以點開頭的文件與以字母開頭的文件混合,以及 午夜指揮官,它使用其內部函數對名稱進行排序,將以點開頭的檔案放在清單的開頭。

引用

報告10號Unicode排序演算法

unicode.org 上的字元權重

重症監護室 — IBM 的用於使用 Unicode 的函式庫的實作。

排序測試使用 重症監護室

字符權重為 品質政策 ISO 14651

帶刻度的文件格式說明 品質政策 ISO 14652

中字串比較的討論 glibc的

來源: www.habr.com

添加評論