perluniintro - Perl Unicode 簡介
本文件提供 Unicode 的一般概念,以及如何在 Perl 中使用 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 位元字元來表示世界上所有的字元;也就是說,從0x0000
到0xFFFF
,最多需要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 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 同時支援 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。
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。本文檔中即為此情況。
本節完全適用於 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 大多是透明的:只需像往常一樣使用字串即可。index()
、length()
和 substr()
等函式會在 Unicode 字元上運作;正規表示式會在 Unicode 字元上運作(請參閱 perlunicode 和 perlretut)。
請注意,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 資料
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::encoding 和 Encode::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-r
和 KOI8R
都會被理解。
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 的 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{}
,因為您最有可能想要查看原生值為何。)
從 Perl 5.28 開始,對於位元運算子(例如 ~
)在包含大於 255 的字元碼點的字串上運算,是非法的。
如果對包含序數值大於 255 的字元的字串使用 vec() 函式,可能會產生令人意外的結果。在這種情況下,結果與字元的內部編碼一致,但與其他大部分情況不一致。因此,請不要這樣做,從 Perl 5.28 開始,如果您這樣做,會發出棄用訊息,在 Perl 5.32 中會變成非法。
窺探 Perl 的內部編碼
Perl 的一般使用者永遠不必在意 Perl 如何編碼任何特定 Unicode 字串(因為透過輸入和輸出取得 Unicode 字串內容的正常方式,應始終透過明確定義的 I/O 層)。但如果您必須這麼做,有兩種方法可以窺探幕後。
窺探 Unicode 字元內部編碼的一種方法是使用 unpack("C*", ...)
取得字串編碼的位元組,或使用 unpack("U0..", ...)
取得 UTF-8 編碼的位元組
# this prints c4 80 for the UTF-8 bytes 0xc4 0x80
print join(" ", unpack("U0(H2)*", pack("U", 0x100))), "\n";
另一種方法是使用 Devel::Peek 模組
perl -MDevel::Peek -e 'Dump(chr(0x100))'
這顯示 FLAGS 中的 UTF8
標記,以及 PV
中的 UTF-8 位元組和 Unicode 字元。請參閱本文稍後的 utf8::is_utf8()
函數討論。
字串等價
字串等價的問題在 Unicode 中變得有點複雜:您所謂的「相等」是什麼意思?
(帶有銳音符號的拉丁大寫字母 A
是否等於 拉丁大寫字母 A
?)
簡短的答案是,預設情況下 Perl 僅根據字元的碼點來比較等價性(eq
、ne
)。在上述情況下,答案是否定的(因為 0x00C1 != 0x0041)。但有時,任何大寫字母 A 都應視為相等,甚至任何大小寫的 A 都應視為相等。
長篇的答案是,您需要考慮字元正規化和大小寫問題:請參閱 Unicode::Normalize、Unicode 技術報告 #15、Unicode 正規化格式以及 Unicode 標準 中關於大小寫對應的部分。
從 Perl 5.8.0 開始,實作了 大小寫對應/特殊大小寫 的「完整」大小寫轉換,但 qr//i
中仍然存在錯誤,大多數已在 5.14 中修復,而 5.18 中則已完全修復。
字串校對
人們喜歡看到他們的字串經過良好排序,或者用 Unicode 術語來說,經過校對。但同樣地,您所謂的校對是什麼意思?
(帶有銳音符號的拉丁大寫字母 A
出現在 帶有重音符號的拉丁大寫字母 A
之前還是之後?)
簡短的答案是,預設情況下,Perl 僅根據字元的碼點來比較字串(lt
、le
、cmp
、ge
、gt
)。在上述情況下,答案是「之後」,因為 0x00C1
> 0x00C0
。
長篇的答案是「取決於」,而且在不知道(至少)語言環境的情況下,無法給出好的答案。請參閱 Unicode::Collate 和 Unicode 校對演算法 https://www.unicode.org/reports/tr10/
字元範圍和類別
正規表示式中方括弧字元類別 (例如 /[a-z]/
) 和 tr///
(也稱為 y///
) 算子中的字元範圍並非神奇地支援 Unicode。這表示 [A-Za-z]
並不會神奇地開始表示「所有字母」(即使對於 8 位元字元來說,這也不是它的意思;對於這些字元,如果您使用的是語言環境 (perllocale),請使用 /[[:alpha:]]/
;如果不是,請使用支援 8 位元的屬性 \p{alpha}
)。
所有以 \p
開頭的屬性 (及其反向 \P
) 實際上都是支援 Unicode 的字元類別。它們有數十種,請參閱 perluniprops。
從 v5.22 開始,您可以使用 Unicode 碼點作為正規表示式模式字元範圍的終點,而範圍將包含介於這些終點之間的所有 Unicode 碼點(含)。
qr/ [ \N{U+03} - \N{U+20} ] /xx
包含碼點 \N{U+03}
、\N{U+04}
、...、\N{U+20}
。
這也適用於 Perl v5.24 中 tr///
的範圍。
字串轉數字轉換
Unicode 除了熟悉的 0 到 9 之外,還定義了其他幾個十進位和數字字元,例如阿拉伯數字和印度數字。Perl 不支援將 ASCII 0
到 9
(以及十六進位的 ASCII a
到 f
) 以外的數字轉換為數字。若要從任何 Unicode 字串取得安全的轉換,請使用 "num()" in Unicode::UCD。
我的舊腳本會中斷嗎?
很可能不會。除非您以某種方式產生 Unicode 字元,否則舊行為應予保留。唯一已變更且可能開始產生 Unicode 的行為是 chr()
的舊行為,其中提供大於 255 的引數會產生模數為 255 的字元。例如,chr(300)
等於 chr(45)
或「-」(在 ASCII 中),現在它是帶短音符的拉丁大寫字母 I。
我如何讓我的腳本支援 Unicode?
由於在產生 Unicode 資料之前,不會有任何變更,因此幾乎不需要任何工作。最重要的部分是將輸入轉換為 Unicode;有關這一點,請參閱先前的 I/O 討論。若要取得完整的無縫 Unicode 支援,請將 use feature 'unicode_strings'
(或 use v5.12
或更高版本)新增至您的指令碼。
如何得知我的字串是否為 Unicode?
您不應該在意。但如果您使用的是 5.14.0 之前的 Perl,或者您尚未指定 use feature 'unicode_strings'
或 use 5.012
(或更高版本),則您可能會在意,因為否則範圍 128 至 255 中的碼位規則會有所不同,具體取決於它們所包含的字串是否為 Unicode。(請參閱 perlunicode 中的「Unicode 未發生時」)。
若要判斷字串是否為 Unicode,請使用
print utf8::is_utf8($string) ? 1 : 0, "\n";
但請注意,這並不表示字串中的任何字元都是必要的 UTF-8 編碼,或任何字元的碼位都大於 0xFF (255) 或甚至 0x80 (128),或字串有任何字元。is_utf8()
所做的只是傳回附加至 $string
的內部「utf8ness」旗標的值。如果旗標關閉,則標量中的位元組會被解釋為單一位元組編碼。如果旗標開啟,則標量中的位元組會被解釋為字元的(可變長度、可能是多位元組)UTF-8 編碼碼位。新增至 UTF-8 編碼字串的位元組會自動升級為 UTF-8。如果混合非 UTF-8 和 UTF-8 標量(雙引號內插、明確串接或 printf/sprintf 參數替換),則結果將會編碼為 UTF-8,就像位元組字串的副本已升級為 UTF-8:例如,
$a = "ab\x80c";
$b = "\x{100}";
print "$a = $b\n";
輸出字串將會是 UTF-8 編碼的 ab\x80c = \x{100}\n
,但 $a
將會維持位元組編碼。
有時您可能真的需要知道字串的位元組長度,而不是字元長度。對於這種情況,請使用 bytes
實用程式和 length()
函數
my $unicode = chr(0x100);
print length($unicode), "\n"; # will print 1
use bytes;
print length($unicode), "\n"; # will print 2
# (the 0xC4 0x80 of the UTF-8)
no bytes;
如何找出檔案的編碼為何?
您可以嘗試 Encode::Guess,但它有一些限制。
如何偵測在特定編碼中無效的資料?
使用 Encode
套件嘗試轉換它。例如,
use Encode 'decode';
if (eval { decode('UTF-8', $string, Encode::FB_CROAK); 1 }) {
# $string is valid UTF-8
} else {
# $string is not valid UTF-8
}
或使用 unpack
嘗試解碼
use warnings;
@chars = unpack("C0U*", $string_of_bytes_that_I_think_is_utf8);
如果無效,會產生 Malformed UTF-8 character
警告。「C0」表示「逐字元處理字串」。沒有它,unpack("U*", ...)
會在 U0
模式下運作(如果格式字串以 U
開頭,則為預設值),它會傳回組成目標字串 UTF-8 編碼的位元組,這總是會運作。
如何將二進位資料轉換成特定編碼,或反之亦然?
這可能不如你想像的有用。通常,你不需要這樣做。
從某種意義上來說,你的問題不太合理:編碼是針對字元的,而二進位資料不是「字元」,因此在不知道二進位資料的字元集和編碼的情況下,將「資料」轉換成某種編碼是沒有意義的,這種情況下它不只是二進位資料,對吧?
如果你有一連串原始位元組,你知道應該透過特定編碼來解讀,你可以使用 Encode
use Encode 'from_to';
from_to($data, "iso-8859-1", "UTF-8"); # from latin-1 to UTF-8
呼叫 from_to()
會變更 $data
中的位元組,但就 Perl 而言,字串的實質性質並未改變。在呼叫前後,字串 $data
都只包含一堆 8 位元組。就 Perl 而言,字串的編碼仍然是「系統原生 8 位元組」。
你可以將這與虛構的「翻譯」模組聯想在一起
use Translate;
my $phrase = "Yes";
Translate::from_to($phrase, 'english', 'deutsch');
## phrase now contains "Ja"
字串的內容會變更,但字串的性質不會。在呼叫之後,Perl 並不知道字串的內容表示肯定,就像呼叫之前一樣。
回到資料轉換。如果你有(或想要)系統原生 8 位元編碼(例如 Latin-1、EBCDIC 等)的資料,你可以使用 pack/unpack 來轉換成 Unicode 或從 Unicode 轉換。
$native_string = pack("W*", unpack("U*", $Unicode_string));
$Unicode_string = pack("U*", unpack("W*", $native_string));
如果你有一連串位元組,你知道它是有效的 UTF-8,但 Perl 還不知道,你可以讓 Perl 也相信
$Unicode = $bytes;
utf8::decode($Unicode);
或
$Unicode = pack("U0a*", $bytes);
你可以使用以下方式找出組成 UTF-8 序列的位元組
@bytes = unpack("C*", $Unicode_string)
你可以使用以下方式建立格式良好的 Unicode
$Unicode_string = pack("U*", 0xff, ...)
如何顯示 Unicode?如何輸入 Unicode?
請參閱 http://www.alanwood.net/unicode/ 和 http://www.cl.cam.ac.uk/~mgk25/unicode.html
Unicode 如何與傳統地區設定搭配使用?
如果你的地區設定是 UTF-8 地區設定,從 Perl v5.26 開始,Perl 在所有類別中都能順利運作;在此之前,從 Perl v5.20 開始,它在所有類別中都能運作,但 LC_COLLATE
除外,後者處理排序和 cmp
運算子。但請注意,標準的 Unicode::Collate
和 Unicode::Collate::Locale
模組提供了更強大的整理問題解決方案,並可在較早的版本中運作。
對於其他地區設定,從 Perl 5.16 開始,你可以指定
use locale ':not_characters';
讓 Perl 能順利與它們搭配使用。問題是你必須自己從地區設定字元集轉換成 Unicode 或從 Unicode 轉換回來。請參閱上方的 "Unicode I/O" 了解如何執行此操作
use open ':locale';
但在 "perllocale 中的 Unicode 和 UTF-8" 中有完整的詳細資訊,包括如果你未指定 :not_characters
會發生的問題。
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
Unicode Consortium
Unicode 常見問題
Unicode 字彙
Unicode 推薦閱讀清單
Unicode Consortium 提供一連串文章和書籍,其中一些提供更深入的 Unicode 說明:http://unicode.org/resources/readinglist.html
Unicode 實用資源
Unicode 和 HTML、字型、網路瀏覽器及其他應用程式中的多語言支援
Unix/Linux 的 UTF-8 和 Unicode 常見問題
舊式字元集
您可以使用 Unicode::UCD
模組瀏覽 Unicode 資料檔案中的各種資訊。
如果您無法將 Perl 升級到 5.8.0 或更新版本,您仍然可以使用 Unicode::String
、Unicode::Map8
和 Unicode::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;
perlunitut、perlunicode、Encode、open、utf8、bytes、perlretut、perlrun、Unicode::Collate、Unicode::Normalize、Unicode::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 相同的條款下散布。