Perl with UTF-8 mode
發現其實我沒對這個作筆記,剛好和 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::TW 與 Encode::CN 的說明,針對 big5 與 gb2312 編碼,在 perl 裡,我們可以使用 big5 與 gb2312 來處理網頁、郵件通常會遇到的字碼。不過,為了因應大部分人的使用習慣 (windows user),或許用 cp950 與 cp936 更不會碰到麻煩。
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 也不遲。
- 在 HTML 裡可以以
€表示。 ↩
Random Posts
- None Found
Similar Posts
- None Found
5 Comments
說明得實在太詳細了,大感謝! :D
換到 utf8 雖然是個不錯的主意,不過即使留在 big5 陣營,第一段程式仍然有解。只要善用 Perl 的 quotemeta(),就不必自己弄一堆醜醜的十六進位碼:
<span class="code">
<pre>#!/usr/bin/perl -w
# Source encoded in big5.
my $s = '英雄人物';
my $regex = quotemeta('英');
if ($s =~ m/$regex/o) {
print "是我\n";
}
else {
print "不是我\n";
}
</pre>
</span>
William,
十分感謝,確實是個好方法,且少掉了多次 big5 <=> utf8 轉碼,程式應該會更有效率些。
#!/usr/bin/perl -w
# Source encoded in big5.
my $s = '英雄人物';
if ($s =~ m/\Q英\E/o) {
print "是我n";
}
else {
print "不是我n";
}
也可以用 use encoding 叫 perl 把之後的編碼全視做 big5...
$ cat test.pl
#!/usr/bin/perl -w
# Source encoded in big5.
use encoding big5;
my $s = '英雄人物';
if ($s =~ m/英/o) {
print "是我\n";
}
else {
print "不是我\n";
}
$ perl test.pl
是我
2 Backlinks
Perl with UTF-8 mode Parsing a Querystring With Perl - A Simple ISINDEX Query (Page 2 of 3 ) What’s in a User-Agent String?
Perl with UTF-8 mode [JeffHung.Blog]
Post a Comment