發現其實我沒對這個作筆記,剛好和 PipperL 在他的 blog 裡聊到,就順便作個簡單的說明好了。


問題描述

為什麼要用 UTF-8 mode 執行 perl 呢?因為 Perl 的字串預設是 byte string,對於使用 ASCII 的人來說,沒有影響,但對於 CJK 使用者來說,就很麻煩。舉例來說,以下程式使用的 regular expression 會無法正確 match 出「英」字,因為其 big5 碼的第二個 byte 是 '^' 符號,導致 regular expression 錯誤:

SHELL> more -x4 plain.pl#!/usr/bin/perl -w# Source encoded in big5.my $s = '英雄人物';if ($s =~ m/英/o) {    print "是我\n";}else {    print "不是我\n";}

SHELL> ./plain.pl不是我

因此,我們必須寫成彆扭的:

SHELL> echo '英' | hexdump -C00000000  ad 5e 0a                                          |.^.|00000003

SHELL> more -x4 hacked.pl#!/usr/bin/perl -w# Source encoded in big5.

my $s = '英雄人物';if ($s =~ m/[\xAD\x5E]/o) { # 英    print "是我\n";}else {    print "不是我\n";}

如果,perl 程式能夠在 regular expression 裡使用 Unicode,那就沒有這個問題了。解法 請用 perl 5.8.6 以上,在程式最前面下:

use utf8;

這樣子程式裡面所有字串都是使用 utf8 編碼,若有需要,再在特定 block 裡用 use bytes 切回使用 byte string。這樣,上面的程式就可以正常運作了:

SHELL> more -x4 u8mode.pl#!/usr/bin/perl -w# Source encoded in utf8.use utf8;my $s = '英雄人物';if ($s =~ m/英/o) {    print "是我\n";}else {    print "不是我\n";}

SHELL> ./u8mode.plWide character in print at ./u8mode.pl line 11.是我

不過,在 use utf8 之後,Perl I/O 也會假設外面也是用 utf8,但通常讀進來或要寫出去的,是 big5,所以會跑出「Wide character in print at ./u8mode.pl line 6.」的訊息出來。因此,我們要加寫這幾行,讓 perl 知道外面是用哪一種 encoding:

binmode(STDIN, ':encoding(big5)');binmode(STDOUT, ':encoding(big5)');binmode(STDERR, ':encoding(big5)');

如果有自己開的檔,也比照辦理。最終的版本如下:

SHELL> more -x4 u8mode.pl#!/usr/bin/perl -w# Source encoded in utf8.use utf8;binmode(STDIN, ':encoding(big5)');binmode(STDOUT, ':encoding(big5)');binmode(STDERR, ':encoding(big5)');my $s = '英雄人物';if ($s =~ m/英/o) {    print "是我\n";}else {    print "不是我\n";}

SHELL> ./u8mode.pl是我

另外要注意的是,use utf8 只能對 perl 本身提供的語言機制產生作用,對於 3rd party libraries 不一定有作用。因此,使用 DBI 搭配 use utf8 時,要記得 fetchrow_xxxx() 得到的東西,要特別因應原始來源的 encoding 作處理,利用 _utf8_on()_utf8_off() 直接設定字串的 utf8 flag,以免重複轉碼或沒有轉碼。詳情請 perldoc Encode,在此就不再多述。

2007-02-27 補充:

研究了一下在 perl 裡,正/簡體中文編碼叫什麼。

依據 Encode::TWEncode::CN 的說明,針對 big5 與 gb2312 編碼,在 perl 裡,我們可以使用 big5gb2312 來處理網頁、郵件通常會遇到的字碼。不過,為了因應大部分人的使用習慣 (windows user),或許用 cp950cp936 更不會碰到麻煩。

Encode::TW 是這麼說的:

Since the original "big5" encoding (1984) is not supported anywhere (glibc and DOS-based systems uses "big5" to mean "big5-eten"; Microsoft uses "big5" to mean "cp950"), a conscious decision was made to alias "big5" to "big5-eten", which is the de facto superset of the original big5.

通常來說,在 UNIX 裡我們用的 big5 是 big5-eten,在 Windows 下,用的則是 cp950,cp950 是 Code Page 950 的意思,只比 big5-eten 還多了一個歐元符號 €[1]

而 Encode::CN 則是這麼說的:

When you see "charset=gb2312" on mails and web pages, they really mean "euc-cn" encodings.  To fix that, "gb2312" is aliased to "euc-cn".  Use "gb2312-raw" when you really mean it.

所以,通常在處理網頁、郵件時,我們應該用 gb2312 來處理簡體中文編碼。我不清楚 euc-cn 與 cp936 之間的差別,不過我想,就跟 big5-eten 與 cp950 的關係一樣,一般來說用 gb2312 (即 euc-cn) 應該就夠了,如果要真的很龜毛的話,再來用 cp936 也不遲。


  1. 在 HTML 裡可以以 € 表示。