perlunifaq - Perl Unicode 常見問答集
這是一份關於 Perl 中 Unicode 的問題與解答清單,建議在閱讀 perlunitut 後閱讀。
沒錯,這也不是真正的 Unicode 常見問答集。
Perl 有一個抽象介面,適用於所有支援的字元編碼,因此這實際上是一個通用的 Encode
教學課程和 Encode
常見問答集。但許多人認為 Unicode 很特別且神奇,我不想讓他們失望,所以我決定將這份文件稱為 Unicode 教學課程。
若要找出 Perl 支援哪些字元編碼,請執行
perl -MEncode -le "print for Encode->encodings(':all')"
嗯,如果你可以,請升級到最新版本,但一定要是 5.8.1
或更新版本。本教學課程和常見問答集假設使用最新版本。
你還應該檢查你的模組,並在需要時升級它們。例如,HTML::Entities 需要版本 >= 1.32 才能正常運作,即使變更日誌中沒有提到這一點。
嗯,除了單純的 binmode $fh
之外,你不應該特別處理它們。(需要 binmode,否則 Perl 可能會在 Win32 系統上轉換行尾。)
不過,請小心不要將文字字串與二進位字串結合。如果你需要在二進位串流中加入文字,請先使用適當的編碼對文字字串進行編碼,然後再將它們與二進位字串結合。另請參閱:「如果我不編碼會怎樣?」
每當你與 Perl 程序外部的任何事物(例如資料庫、文字檔案、socket 或其他程式)進行文字通訊時。即使你通訊的對象也是用 Perl 編寫的。
每當你的編碼二進位字串與文字字串一起使用時,Perl 會假設你的二進位字串使用 ISO-8859-1(也稱為 latin-1)編碼。如果不是 latin-1,你的資料就會被不適當地轉換。例如,如果是 UTF-8,多位元組字元的個別位元組會被視為獨立的字元,然後再轉換為 UTF-8。這種雙重編碼可以比喻成雙重 HTML 編碼(>
)或雙重 URI 編碼(%253E
)。
這種靜默的隱式解碼稱為「升級」。這聽起來可能很正面,但最好避免這種情況。
這取決於您輸出的內容以及輸出的方式。
如果字串的所有字元都是碼點 255 或更低,Perl 會輸出與這些碼點相符的位元組。這是編碼字串會發生的事。不過,也可能發生在所有碼點都為 255 或更低的未編碼字串上。
否則,Perl 會輸出編碼為 UTF-8 的字串。這只會發生在您忽略編碼的字串上。由於不應發生這種情況,因此 Perl 在這種情況下也會拋出「寬字元」警告。
exec
、chdir
、..)您的文字字串將使用 Perl 內部格式中的位元組傳送。
由於內部格式通常是 UTF-8,因此很難發現這些錯誤,因為 UTF-8 通常是您想要的編碼!但不要偷懶,也不要利用 Perl 內部格式為 UTF-8 的事實。明確編碼以避免奇怪的錯誤,並向維護程式設計師展示您已考慮過這一點。
如果來自特定處理程式的所有資料都以完全相同的方式編碼,您可以使用 encoding
層告訴 PerlIO 系統自動解碼所有內容。如果您這樣做,您就無法再意外忘記解碼或編碼,在使用分層處理程式的事物上。
您可以在 open
檔案時提供此層
open my $fh, '>:encoding(UTF-8)', $filename; # auto encoding on write
open my $fh, '<:encoding(UTF-8)', $filename; # auto decoding on read
或者如果您已經有一個開啟的檔案處理程式
binmode $fh, ':encoding(UTF-8)';
DBI 的一些資料庫驅動程式也可以自動編碼和解碼,但有時僅限於 UTF-8 編碼。
盡一切辦法找出,如果必須的話:猜測。(別忘了在註解中記錄您的猜測。)
您可以在網路瀏覽器中開啟文件,並變更字元集或字元編碼,直到您可以在視覺上確認所有字元都顯示為應有的樣子。
沒有辦法可靠地自動偵測編碼,因此如果人們持續在沒有字元集指示的情況下傳送資料給您,您可能必須教育他們。
可以!如果您的來源是 UTF-8 編碼,您可以使用 use utf8
實用程式表示。
use utf8;
這不會對您的輸入或輸出做任何事。它只會影響您的來源被讀取的方式。您可以在字串文字、識別碼(但根據 \w
它們仍然必須是「字元」)中使用 Unicode,甚至可以在自訂分隔符號中使用。
沒有,Data::Dumper 的 Unicode 功能應有盡有。有些人抱怨說,當使用 eval
再次讀取資料時,它應該還原 UTF8 標記。然而,您真的不應該查看標記,而且沒有任何跡象表明 Data::Dumper 應該打破這個規則。
以下是發生的事情:當 Perl 讀取字串文字時,它會盡可能堅持使用 8 位元編碼。(但或許最初在轉儲時,它在內部編碼為 UTF-8。)當它必須放棄,因為其他字元已新增至文字字串時,它會在不聲不響的情況下將字串升級為 UTF-8。
如果您正確編碼輸出字串,這些都不會是您的問題,而且您可以像往常一樣,只使用 eval
轉儲資料。
從 Perl 5.14 開始(以及 Perl 5.12 中的部分功能),只要在程式碼開頭附近放置 use feature 'unicode_strings'
即可。在它的詞彙範圍內,您不應該有這個問題。它也會在 use feature ':5.12'
或 use v5.12
下自動啟用,或在 Perl 5.12 或更高版本中,在命令列上使用 -E
。
需要這樣做的理由是,不要破壞依賴於 Unicode 出現之前運作方式的舊程式碼。那些舊程式碼只知道 ASCII 字元集,因此可能無法正確處理其他字元。當字串以 UTF-8 編碼時,Perl 會假設程式已準備好處理 Unicode,但當字串不是時,Perl 會假設只想要 ASCII,因此那些不是 ASCII 字元的字元不會被辨識為 Unicode 中的字元。use feature 'unicode_strings'
告訴 Perl 將所有字元視為 Unicode,無論字串是否以 UTF-8 編碼,從而避免了這個問題。
然而,在較早的 Perl 版本中,或者如果您將字串傳遞給功能範圍外的子程式,您可以透過將編碼變更為 UTF-8 來強制執行 Unicode 規則,方法是執行 utf8::upgrade($string)
。這可以用於任何字串,因為它會檢查並不會變更已升級的字串。
如需更詳細的討論,請參閱 CPAN 上的 Unicode::Semantics。
請參閱前一個問題的解答。
您無法執行此動作。有些人會使用 UTF8 標記來執行此動作,但這是錯誤的用法,而且會讓 Data::Dumper 等表現良好的模組看起來很差。此標記對此目的而言毫無用處,因為當使用 8 位元編碼(預設為 ISO-8859-1)來儲存字串時,此標記會關閉。
這是您(程式設計師)必須追蹤的項目;很抱歉。您可以考慮採用一種「匈牙利表示法」來協助處理此問題。
首先將 FOO 編碼的位元組字串轉換為文字字串,然後將文字字串轉換為 BAR 編碼的位元組字串
my $text_string = decode('FOO', $foo_string);
my $bar_string = encode('BAR', $text_string);
或略過文字字串部分,直接從一種二進位編碼轉換為另一種編碼
use Encode qw(from_to);
from_to($string, 'FOO', 'BAR'); # changes contents of $string
或讓自動解碼和編碼處理所有工作
open my $foofh, '<:encoding(FOO)', 'example.foo.txt';
open my $barfh, '>:encoding(BAR)', 'example.bar.txt';
print { $barfh } $_ while <$foofh>;
decode_utf8
和 encode_utf8
是什麼?這些是 decode('utf8', ...)
和 encode('utf8', ...)
的替代語法。請勿將這些函式用於資料交換。請改用 decode('UTF-8', ...)
和 encode('UTF-8', ...)
;請參閱下方的「"UTF-8 和 utf8 有什麼不同?"」。
這是用來表示佔用多於一個位元組的字元的術語。
Perl 警告「... 中的寬字元」是由此類字元所導致。在未指定編碼層的情況下,Perl 會嘗試將所有內容塞進一個位元組中。當無法執行此動作時,它會發出此警告(如果已啟用警告),並改用 UTF-8 編碼的資料。
若要避免此警告,並避免在單一串流中出現不同的輸出編碼,請務必明確指定編碼,例如使用 PerlIO 層。
binmode STDOUT, ":encoding(UTF-8)";
請不要思考 UTF8 標記,除非您正在破解內部結構或除錯怪異問題。這表示您很可能根本不應該使用 is_utf8
、_utf8_on
或 _utf8_off
。
UTF8 標記(也稱為 SvUTF8)是一個內部標記,用來表示目前的內部表示法為 UTF-8。如果沒有此標記,則假設為 ISO-8859-1。Perl 會自動在這些編碼之間進行轉換。(實際上,Perl 通常假設表示法為 ASCII;請參閱上方的「"為什麼正規表示式字元類別有時只會在 ASCII 範圍內相符?"」)
Perl 的其中一種內部格式恰好是 UTF-8。很不幸地,Perl 無法保守秘密,所以所有人都知道這件事。這是造成許多混淆的來源。最好假裝內部格式是一種未知的編碼,而且您必須始終明確編碼和解碼。
use bytes
pragma 呢?不要使用它。在文字字串中處理位元組沒有意義,在位元組字串中處理字元也沒有意義。執行適當的轉換(透過解碼/編碼),事情就會順利進行:您會取得已解碼資料的字元計數,以及已編碼資料的位元組計數。
use bytes
通常是嘗試執行某項有用操作的失敗嘗試。只要忘記它就可以了。
use encoding
pragma 呢?不要使用它。很遺憾,它假設程式設計人員的環境和使用者的環境會使用相同的編碼。它會對原始碼和 STDIN 和 STDOUT 使用相同的編碼。當程式複製到另一部電腦時,原始碼不會變更,但 STDIO 環境可能會變更。
如果您需要在原始碼中使用非 ASCII 字元,請將其設為 UTF-8 編碼檔案,並 use utf8
。
如果您需要設定 STDIN、STDOUT 和 STDERR 的編碼,例如根據使用者的地區設定,請 use open
。
:encoding
和 :utf8
之間的差異是什麼?由於 UTF-8 是 Perl 的內部格式之一,因此您通常可以跳過編碼或解碼步驟,並直接操作 UTF8 旗標。
您可以使用 :utf8
取代 :encoding(UTF-8)
,如果資料已在內部表示為 UTF8,則 :utf8
會跳過編碼步驟。在撰寫時,這被廣泛接受為良好的行為,但在閱讀時可能會很危險,因為當您有無效的位元組序列時,它會導致內部不一致。對輸入使用 :utf8
有時可能會導致安全漏洞,因此請改用 :encoding(UTF-8)
。
您可以使用 _utf8_on
和 _utf8_off
取代 decode
和 encode
,但這被認為是不良的風格。特別是 _utf8_on
可能是危險的,原因與 :utf8
相同。
有些捷徑適用於單行;請參閱 -C in perlrun。
UTF-8
和 utf8
之間的差異是什麼?UTF-8
是官方標準。utf8
是 Perl 在接受內容方面採取自由的方式。如果您必須與不太自由的事物進行通訊,您可能需要考慮使用 UTF-8
。如果您必須與太過自由的事物進行通訊,您可能必須使用 utf8
。完整的說明在 "UTF-8 vs. utf8 vs. UTF8" in Encode 中。
UTF-8
在內部稱為 utf-8-strict
。本教學課程始終使用 UTF-8,即使在實際上在內部使用 utf8 的地方也是如此,因為這種區別可能很難做出,而且大多數情況下無關緊要。
例如,utf8 可用於 Unicode 中不存在的代碼點,例如 9999999,但如果你將其編碼為 UTF-8,你會得到一個替換字元(預設;請參閱 編碼中的「處理格式錯誤的資料」 以取得更多處理此問題的方法。)
好吧,如果你堅持:所謂的「內部格式」是 utf8,而不是 UTF-8。(當它不是其他編碼時。)
你迷路很好,因為你不應該依賴內部格式是任何特定編碼。但既然你問了:預設情況下,內部格式是 ISO-8859-1(latin-1)或 utf8,這取決於字串的歷史記錄。在 EBCDIC 平台上,這甚至可能有所不同。
Perl 知道它在內部如何儲存字串,並會在您執行 encode
時使用該知識。換句話說:不要嘗試找出特定字串的內部編碼是什麼,而只需將其編碼為您想要的編碼即可。
Juerd Waalboer <#####@juerd.nl>