內容

名稱

perluniintro - Perl Unicode 簡介

說明

本文件提供 Unicode 的一般概念,以及如何在 Perl 中使用 Unicode。請參閱 "其他資源",以取得更深入探討 Unicode 的參考資料。

Unicode

Unicode 是一個字元集標準,計畫編纂世界上所有的書寫系統,以及許多其他符號。

Unicode 和 ISO/IEC 10646 是協調的標準,統一了幾乎所有其他現代字元集標準,涵蓋 80 多種書寫系統和數百種語言,包括所有商業上重要的現代語言。最大的中、日、韓字典中的所有字元也已編碼。這些標準最終將涵蓋 250 多種書寫系統和數千種語言中的幾乎所有字元。Unicode 1.0 於 1991 年 10 月發布,6.0 於 2010 年 10 月發布。

Unicode 字元 是一種抽象實體。它不受限於任何特定整數寬度,特別是不受 C 語言 char 的限制。Unicode 與語言和顯示無關:它不編碼文字的語言,而且通常不定義字型或其他圖形配置細節。Unicode 運作於字元和由這些字元建構的文字。

Unicode 定義了字元,例如 拉丁文大寫字母 A希臘文小寫字母 ALPHA,以及這些字元的唯一數字,在本例中分別為 0x0041 和 0x03B1。這些唯一數字稱為 碼點。碼點基本上是字元在所有可能的 Unicode 字元集合中的位置,因此在 Perl 中,術語 序數 通常與其互換使用。

Unicode 標準偏好使用十六進位表示法表示碼點。如果您不熟悉 0x0041 等數字,請參閱稍後的部分 "十六進位表示法"。Unicode 標準使用表示法 U+0041 拉丁文大寫字母 A,以提供十六進位碼點和字元的規範名稱。

Unicode 也為字元定義了各種 屬性,例如「大寫」或「小寫」、「十進位數字」或「標點符號」;這些屬性與字元的名稱無關。此外,還定義了各種對字元的運算,例如轉換為大寫、轉換為小寫和對照 (排序)。

Unicode 邏輯「字元」實際上可能包含多個內部 實際「字元」或碼點。對於西方語言,這可以用 基本字元 (例如 拉丁文大寫字母 A) 加上一個或多個 修飾符號 (例如 組合尖音符號) 來充分建模。這種基本字元和修飾符號的序列稱為 組合字元序列。一些非西方語言需要更複雜的模型,因此 Unicode 建立了 音節群集 概念,後來進一步改進為 擴充音節群集。例如,韓語諺文音節被認為是一個單一的邏輯字元,但通常包含三個實際的 Unicode 字元:一個前導輔音,後接一個內部母音,再後接一個尾隨輔音。

是否將這些擴充音節群集稱為「字元」取決於您的觀點。如果您是程式設計師,您可能會傾向於將序列中的每個元素視為一個單位或「字元」。然而,從使用者的觀點來看,整個序列可以視為一個「字元」,因為這可能是它在使用者語言的語境中所呈現的樣子。在本文件中,我們採用程式設計師的觀點:一個「字元」是一個 Unicode 碼點。

對於某些基本字元和變音符號的組合,有預組成字元。例如,對於序列拉丁文大寫字母 A後面接著組合銳音符號,有一個單一字元等價,稱為拉丁文大寫字母 A 加上銳音符號。然而,這些預組成字元僅適用於某些組合,其主要用意是支援 Unicode 和舊有標準(例如 ISO 8859)之間的雙向轉換。使用序列(如 Unicode 所做)可以減少所需的基礎建構區塊(碼位),以表達更多潛在的字形群集。為了支援等效形式之間的轉換,也定義了各種正規化形式。因此,拉丁文大寫字母 A 加上銳音符號處於正規化形式組成(簡稱 NFC),而序列拉丁文大寫字母 A後面接著組合銳音符號正規化形式分解(NFD)中代表相同的字元。

由於與舊有編碼的向下相容性,「每個字元都有唯一數字」的想法有點失真:取而代之的是「每個字元至少有一個數字」。相同的字元可以在幾個舊有編碼中以不同的方式表示。反之則不然:有些碼位沒有分配字元。首先,在其他已使用的區塊中有一些未分配的碼位。其次,有一些特殊的 Unicode 控制字元不代表真正的字元。

當 Unicode 最初構思時,人們認為可以使用 16 位元字元來表示世界上所有的字元;也就是說,從0x00000xFFFF,最多需要0x10000(或 65,536)個字元。這很快就證明是錯誤的,自 Unicode 2.0(1996 年 7 月)以來,Unicode 已定義為最高 21 位元(0x10FFFF),而 Unicode 3.1(2001 年 3 月)定義了第一個高於0xFFFF的字元。第一個0x10000個字元稱為平面 0基本多語言平面(BMP)。隨著 Unicode 3.1,總共定義了 17 個(是的,十七個)平面,但它們還遠遠沒有填滿已定義的字元。

當編碼新語言時,Unicode 通常會為其字元選擇一個連續未分配碼點的區塊。到目前為止,這些區塊中的碼點數量一直都可以被 16 整除。區塊中目前不需要的額外碼點會保留不分配,以供未來擴充。但有時後續版本需要比可用額外碼點更多的碼點,就必須在其他地方分配一個新的區塊,且與最初的區塊不連續,以處理溢位。因此,很早就發現「區塊」並非適當的組織原則,於是建立了字碼屬性。(後來還新增了一個改良的字碼屬性,即字碼延伸屬性。)位於溢位區塊中的那些碼點仍可以與原始碼點具有相同的字碼。字碼概念更符合自然語言:有拉丁語字碼、希臘語字碼等;還有多種人工字碼,例如用於多種字碼中字元的通用字碼,例如數學符號。字碼通常橫跨多個區塊的不同部分。如需有關字碼的更多資訊,請參閱perlunicode 中的「字碼」。區塊劃分是存在的,但它幾乎完全是偶然的,是字元分配方式的產物。(請注意,為了作為引言,本段落過度簡化了內容。Unicode 實際上並非編碼語言,而是編碼語言的書寫系統,也就是字碼;而且一個字碼可以用於多種語言。Unicode 也編碼與語言無關的事物,例如行李領取等符號。)

Unicode 碼點只是抽象數字。若要輸入和輸出這些抽象數字,必須以某種方式編碼序列化這些數字。Unicode 定義了多種字元編碼形式,其中UTF-8 最為普及。UTF-8 是一種可變長度編碼,將 Unicode 字元編碼為 1 到 4 個位元組。其他編碼包括 UTF-16 和 UTF-32,以及它們的大端和低端變體(UTF-8 與位元組順序無關)。ISO/IEC 10646 定義了 UCS-2 和 UCS-4 編碼形式。

如需有關編碼的更多資訊,例如了解代理字元位元組順序標記 (BOM) 是什麼,請參閱perlunicode

Perl 的 Unicode 支援

從 Perl v5.6.0 開始,Perl 就有能力原生處理 Unicode。然而,Perl v5.8.0 是第一個建議用於嚴肅 Unicode 作業的版本。維護版本 5.6.1 修復了最初 Unicode 實作的許多問題,但例如正規表示式在 5.6.1 中仍無法與 Unicode 搭配使用。Perl v5.14.0 是第一個 Unicode 支援(幾乎)可以無縫整合且沒有陷阱的版本。(有少數例外。首先,quotemeta 中的一些差異從 Perl 5.16.0 開始修復。其次,範圍運算子 中的一些差異從 Perl 5.26.0 開始修復。第三,split 中的一些差異從 Perl 5.28.0 開始修復。)

若要啟用此無縫支援,您應該使用功能「unicode_strings」(如果您使用 v5.12或更高版本,會自動選取此功能)。請參閱功能。(5.14 也修正了許多錯誤,並修正了與 Unicode 標準的差異。)

在 Perl v5.8.0 之前,使用use utf8用於宣告當前區塊或檔案中的作業會辨識 Unicode。此模型被發現有誤,或至少很笨拙:「Unicode 性」現在會與資料一起傳遞,而不是附加到作業中。從 Perl v5.8.0 開始,只在需要明確的use utf8時才會保留一個案例:如果您的 Perl 程式碼本身是以 UTF-8 編碼,您可以在識別名稱中,以及字串和正規表示式文字中使用 UTF-8,方法是說use utf8。這不是預設值,因為其中有舊版 8 位元資料的程式碼會中斷。請參閱utf8

Perl 的 Unicode 模型

Perl 同時支援 5.6 之前的八位元原生位元組字串,以及 Unicode 字元字串。一般原則是 Perl 嘗試盡可能將其資料保留為八位元位元組,但只要無法避免 Unicode,資料就會透明地升級為 Unicode。在 Perl v5.14.0 之前,升級並非完全透明(請參閱perlunicode 中的「Unicode 錯誤」),而且為了向後相容性,除非選取use feature 'unicode_strings'(請參閱功能)或use v5.12(或更高版本),否則不會獲得完全的透明性。

在內部,Perl 目前使用平台的原生八位元字元組集(例如 Latin-1),預設為 UTF-8,來編碼 Unicode 字串。特別是,如果字串中的所有碼點都小於或等於 0xFF,Perl 會使用原生八位元字元組集。否則,它會使用 UTF-8。

Perl 使用者通常不需要知道或關心 Perl 如何編碼其內部字串,但當輸出 Unicode 字串到沒有 PerlIO 層(具有「預設」編碼)的串流時,這就變得相關。在這種情況下,會使用內部使用的原始位元組(原生字元組集或 UTF-8,視每個字串而定),如果這些字串包含 0x00FF 之外的字元,則會發出「寬字元」警告。

例如,

perl -e 'print "\x{DF}\n", "\x{0100}\x{DF}\n"'

會產生原生位元組和 UTF-8 的相當無用的混合,以及一個警告

Wide character in print at ...

若要輸出 UTF-8,請使用:encoding:utf8輸出層。加上前置

binmode(STDOUT, ":utf8");

此範例程式確保輸出完全為 UTF-8,並移除程式的警告。

您可以使用 -C 命令列開關或 PERL_UNICODE 環境變數啟用標準檔案控制代碼、預設 open() 層和 @ARGV 的自動 UTF-8 化,請參閱 perlrun 以取得 -C 開關的說明文件。

請注意,這表示 Perl 預期其他軟體以相同方式運作:如果 Perl 已被誤導為 STDIN 應該是 UTF-8,但來自其他命令的 STDIN 不是 UTF-8,則 Perl 可能會抱怨格式錯誤的 UTF-8。

結合 Unicode 和 I/O 的所有功能也需要使用新的 PerlIO 功能。幾乎所有 Perl 5.8 平台都使用 PerlIO:您可以執行「perl -V」並尋找 useperlio=define 來查看您的平台是否使用 PerlIO。

Unicode 和 EBCDIC

Perl 5.8.0 新增了對 EBCDIC 平台上 Unicode 的支援。此支援在後續版本中被取消,但在 5.22 中又重新啟用。Unicode 支援的實作較為複雜,因為需要額外的轉換。請參閱 perlebcdic 以取得更多資訊。

在 EBCDIC 平台上,內部 Unicode 編碼形式是 UTF-EBCDIC,而不是 UTF-8。兩者的差別在於 UTF-8 是「ASCII 安全」,因為 ASCII 字元會原樣編碼為 UTF-8,而 UTF-EBCDIC 是「EBCDIC 安全」,因為所有基本字元(包括所有具有 ASCII 等效項的字元(例如 "A""0""%" 等))在 EBCDIC 和 UTF-EBCDIC 中都是相同的。文件通常會使用「UTF-8」一詞來表示 UTF-EBCDIC。本文檔中即為此情況。

建立 Unicode

本節完全適用於 v5.22 起始的 Perl。早期版本的各種注意事項請參閱下方 「早期版本注意事項」 小節。

要在字串常數中建立 Unicode 字元,請在雙引號字串中使用 \N{...} 表示法

my $smiley_from_name = "\N{WHITE SMILING FACE}";
my $smiley_from_code_point = "\N{U+263a}";

類似地,它們可用於正規表示式字串常數

$smiley =~ /\N{WHITE SMILING FACE}/;
$smiley =~ /\N{U+263a}/;

或從 v5.32 開始

$smiley =~ /\p{Name=WHITE SMILING FACE}/;
$smiley =~ /\p{Name=whitesmilingface}/;

在執行階段,您可以使用

use charnames ();
my $hebrew_alef_from_name
                     = charnames::string_vianame("HEBREW LETTER ALEF");
my $hebrew_alef_from_code_point = charnames::string_vianame("U+05D0");

當然,ord() 會執行相反的動作:它會將字元轉換為碼點。

也有其他執行階段選項。您可以使用 pack()

my $hebrew_alef_from_code_point = pack("U", 0x05d0);

或者您可以使用 chr(),儘管在一般情況下不太方便

$hebrew_alef_from_code_point = chr(utf8::unicode_to_native(0x05d0));
utf8::upgrade($hebrew_alef_from_code_point);

如果參數大於 0xFF,則不需要 utf8::unicode_to_native()utf8::upgrade(),因此上述內容可以寫成

$hebrew_alef_from_code_point = chr(0x05d0);

因為 0x5d0 大於 255。

\x{}\o{} 也可用於在雙引號字串中於編譯階段指定碼點,但為了與舊版 Perl 的向下相容性,小於 256 的碼點與 chr() 適用相同的規則。

utf8::unicode_to_native() 用於讓 Perl 程式碼可移植到 EBCDIC 平台。如果您真的確定沒有人會在非 ASCII 平台上使用您的程式碼,則可以省略它。從 Perl v5.22 開始,在 ASCII 平台上對它的呼叫會進行最佳化,因此在新增它時完全沒有效能損失。或者,您也可以簡單地使用不需要它的其他建構函式。

請參閱 "進一步資源",以了解如何尋找所有這些名稱和數字代碼。

較早版本注意事項

在 EBCDIC 平台上,v5.22 之前,使用 \N{U+...} 無法正常運作。

在 v5.16 之前,使用 \N{...} 搭配字元名稱(與 U+... 碼點相反)需要 use charnames :full

在 v5.14 之前,\N{...} 搭配字元名稱(與 U+... 碼點相反)有一些錯誤。

charnames::string_vianame() 在 v5.14 中引入。在此之前,charnames::vianame() 應該可以運作,但前提是引數的格式為 "U+..."。您在那裡根據字元名稱執行執行時期 Unicode 的最佳選擇可能是

use charnames ();
my $hebrew_alef_from_name
                 = pack("U", charnames::vianame("HEBREW LETTER ALEF"));

處理 Unicode

處理 Unicode 大多是透明的:只需像往常一樣使用字串即可。index()length()substr() 等函式會在 Unicode 字元上運作;正規表示式會在 Unicode 字元上運作(請參閱 perlunicodeperlretut)。

請注意,Perl 將音節叢集視為獨立字元,因此例如

print length("\N{LATIN CAPITAL LETTER A}\N{COMBINING ACUTE ACCENT}"),
      "\n";

會印出 2,而不是 1。唯一的例外是正規表示式具有 \X,用於比對延伸音節叢集。(因此,正規表示式中的 \X 會比對兩個範例字元的整個順序。)

然而,在處理舊版編碼、I/O 和某些特殊情況時,生活並非如此透明

舊版編碼

當您結合舊版資料和 Unicode 時,需要將舊版資料升級為 Unicode。通常假設舊版資料為 ISO 8859-1(或 EBCDIC,如果適用)。

Encode 模組知道許多編碼,並具有在這些編碼之間進行轉換的介面

use Encode 'decode';
$data = decode("iso-8859-3", $data); # convert from legacy

Unicode I/O

通常,寫出 Unicode 資料

print FH $some_string_with_unicode, "\n";

會產生 Perl 用於內部編碼 Unicode 字串的原始位元組。Perl 的內部編碼取決於系統以及字串中當時包含哪些字元。如果任何字元位於碼點 0x100 或以上,您將會收到警告。若要確保輸出明確地以您想要的編碼呈現,並避免警告,請使用所需的編碼開啟串流。一些範例

open FH, ">:utf8", "file";

open FH, ">:encoding(ucs2)",      "file";
open FH, ">:encoding(UTF-8)",     "file";
open FH, ">:encoding(shift_jis)", "file";

並在已開啟的串流上,使用 binmode()

binmode(STDOUT, ":utf8");

binmode(STDOUT, ":encoding(ucs2)");
binmode(STDOUT, ":encoding(UTF-8)");
binmode(STDOUT, ":encoding(shift_jis)");

編碼名稱的比對很寬鬆:大小寫無關緊要,而且許多編碼有幾個別名。請注意,:utf8 層必須始終像這樣明確指定;它不會受到編碼名稱寬鬆比對的影響。另請注意,目前 :utf8 對於輸入並不安全,因為它會接受資料,而不會驗證它是否確實是有效的 UTF-8;您應該改用 :encoding(UTF-8)(帶或不帶連字號)。

請參閱 PerlIO 以取得 :utf8 層、PerlIO::encodingEncode::PerlIO 以取得 :encoding() 層,以及 Encode::Supported 以取得 Encode 模組支援的許多編碼。

讀取已知使用 Unicode 或舊版編碼編碼的文件時,並不會神奇地將資料轉換為 Perl 眼中的 Unicode。若要執行此操作,請在開啟檔案時指定適當的層

open(my $fh,'<:encoding(UTF-8)', 'anything');
my $line_of_unicode = <$fh>;

open(my $fh,'<:encoding(Big5)', 'anything');
my $line_of_unicode = <$fh>;

I/O 層也可以使用 open 實用程式更靈活地指定。請參閱 open,或查看以下範例。

use open ':encoding(UTF-8)'; # input/output default encoding will be
                             # UTF-8
open X, ">file";
print X chr(0x100), "\n";
close X;
open Y, "<file";
printf "%#x\n", ord(<Y>); # this should print 0x100
close Y;

使用 open 實用程式,您可以使用 :locale

BEGIN { $ENV{LC_ALL} = $ENV{LANG} = 'ru_RU.KOI8-R' }
# the :locale will probe the locale environment variables like
# LC_ALL
use open OUT => ':locale'; # russki parusski
open(O, ">koi8");
print O chr(0x430); # Unicode CYRILLIC SMALL LETTER A = KOI8-R 0xc1
close O;
open(I, "<koi8");
printf "%#x\n", ord(<I>), "\n"; # this should print 0xc1
close I;

這些方法會在 I/O 串流上安裝一個透明的篩選器,在從串流讀取資料時將資料從指定的編碼轉換。結果永遠是 Unicode。

open 實用程式會透過設定預設層來影響實用程式之後的所有 open() 呼叫。如果您只想影響特定串流,請直接在 open() 呼叫中使用明確的層。

您可以使用 binmode() 切換已開啟串流的編碼;請參閱 "binmode" in perlfunc

:locale 目前無法與 open()binmode() 搭配使用,只能與 open 實用程式搭配使用。:utf8:encoding(...) 方法可以搭配所有 open()binmode()open 實用程式使用。

同樣地,您可以在輸出串流上使用這些 I/O 層,在將 Unicode 寫入串流時自動將 Unicode 轉換為指定的編碼。例如,以下程式碼片段會將檔案「text.jis」(編碼為 ISO-2022-JP,又稱 JIS)的內容複製到檔案「text.utf8」,編碼為 UTF-8

open(my $nihongo, '<:encoding(iso-2022-jp)', 'text.jis');
open(my $unicode, '>:utf8',                  'text.utf8');
while (<$nihongo>) { print $unicode $_ }

open()open 實用程式對編碼的命名允許使用彈性的名稱:koi8-rKOI8R 都會被理解。

ISO、MIME、IANA 和其他各種標準化組織所識別的常見編碼會被識別;如需更詳細的清單,請參閱 Encode::Supported

read() 會讀取字元並傳回字元數。seek()tell() 會對位元組計數進行運算,sysseek() 也是如此。

不應在具有字元編碼層的文件處理中使用 sysread()syswrite(),它們的行為不佳,而且這種行為已從 perl 5.24 版開始不建議使用。

請注意,由於預設行為在沒有預設層時不會對輸入進行任何轉換,因此很容易錯誤地撰寫會反覆編碼資料而持續擴充檔案的程式碼

# BAD CODE WARNING
open F, "file";
local $/; ## read in the whole file of 8-bit characters
$t = <F>;
close F;
open F, ">:encoding(UTF-8)", "file";
print F $t; ## convert to UTF-8 on output
close F;

如果您執行此程式碼兩次,檔案 的內容將會被兩次 UTF-8 編碼。use open ':encoding(UTF-8)' 會避免這個錯誤,或明確開啟 檔案 進行 UTF-8 輸入。

注意:utf8:encoding 功能僅在您的 Perl 使用 PerlIO 建置時才有效,這在大部分系統上都是預設值。

將 Unicode 顯示為文字

有時您可能想要將包含 Unicode 的 Perl 標量顯示為簡單的 ASCII(或 EBCDIC)文字。下列子常式會轉換其引數,讓大於 255 的 Unicode 字元顯示為 \x{...},控制字元(例如 \n)顯示為 \x..,其餘字元則顯示為它們自己

sub nice_string {
       join("",
       map { $_ > 255                    # if wide character...
             ? sprintf("\\x{%04X}", $_)  # \x{...}
             : chr($_) =~ /[[:cntrl:]]/  # else if control character...
               ? sprintf("\\x%02X", $_)  # \x..
               : quotemeta(chr($_))      # else quoted or as themselves
       } unpack("W*", $_[0]));           # unpack Unicode characters
  }

例如,

nice_string("foo\x{100}bar\n")

傳回字串

'foo\x{0100}bar\x0A'

已準備好列印。

(在此使用 \\x{} 而不是 \\N{},因為您最有可能想要查看原生值為何。)

特殊狀況

進階主題

其他

有解答的問題

十六進制表示法

Unicode 標準偏好使用十六進制表示法,因為這樣可以更清楚地顯示 Unicode 劃分為 256 個字元的區塊。十六進制也比十進制更簡短。您也可以使用十進制表示法,但學習使用十六進制表示法會讓您在使用 Unicode 標準時更輕鬆。例如,U+HHHH 表示法使用十六進制。

0x 前綴表示十六進制數字,數字為 0-9 a-f(或 A-F,大小寫不重要)。每個十六進制數字代表四個位元,或半個位元組。print 0x..., "\n" 會以十進制顯示十六進制數字,而 printf "%x\n", $decimal 會以十六進制顯示十進制數字。如果您只有十六進制數字的「十六進制數字」,可以使用 hex() 函數。

print 0x0009, "\n";    # 9
print 0x000a, "\n";    # 10
print 0x000f, "\n";    # 15
print 0x0010, "\n";    # 16
print 0x0011, "\n";    # 17
print 0x0100, "\n";    # 256

print 0x0041, "\n";    # 65

printf "%x\n",  65;    # 41
printf "%#x\n", 65;    # 0x41

print hex("41"), "\n"; # 65

進一步的資源

較舊的 Perl 中的 UNICODE

如果您無法將 Perl 升級到 5.8.0 或更新版本,您仍然可以使用 Unicode::StringUnicode::Map8Unicode::Map 模組來執行一些 Unicode 處理,這些模組可從 CPAN 取得。如果您已安裝 GNU recode,您也可以使用 Perl 前端 Convert::Recode 進行字元轉換。

以下是從 ISO 8859-1(Latin-1)位元組到 UTF-8 位元組的快速轉換,以及反向轉換,此程式碼甚至可以在較舊的 Perl 5 版本中執行。

# ISO 8859-1 to UTF-8
s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg;

# UTF-8 to ISO 8859-1
s/([\xC2\xC3])([\x80-\xBF])/chr(ord($1)<<6&0xC0|ord($2)&0x3F)/eg;

另請參閱

perlunitutperlunicodeEncodeopenutf8bytesperlretutperlrunUnicode::CollateUnicode::NormalizeUnicode::UCD

致謝

感謝 perl5-porters@perl.org、perl-unicode@perl.org、linux-utf8@nl.linux.org 和 unicore@unicode.org 郵件清單的熱心讀者提供寶貴的回饋。

作者、版權和授權

版權所有 2001-2011 Jarkko Hietaniemi <jhi@iki.fi>。目前由 Perl 5 Porters 維護。

此文件可在與 Perl 相同的條款下散布。