PerlでUTF-8を扱う
Perlは以前は便利に使っていたが、徐々に使わなくなっていった。
書籍『はじめてのPerl - Perl4対応版』で覚えてLinux管理に大活躍してはいたのだが、
- 複雑なデータ構造を扱うのがたいへん(配列の各要素がhashのkeyになってるようなデータをもたせようとすると、referenceをdereferenceするのが複雑すぎた)
- UTF-8の扱いが理解できない
という問題があったため、最近は距離を置いていた。
だが、one-linerを書く言語としては未だPerlが一番よいように見えた。Node.jsはSTDINの扱いが面倒すぎるとわかった。Pythonはone-liner向きではないし、Rubyは ' end; end; end;'が読みにくそうだ。
perlはUnix環境やCygwinで最初からインストール済みなことが多いのもいい。
そこでPerlでUTF-8扱う方法をしっかり身につけることにした。
Web上の資料を読む
PerlでUTF-8を扱う部分は dankogai が開発したらしい。なので、彼のブログを読むのがよいはずだ。
読んでわかったこと:
- PerlソースコードはUTF-8 BOMなしで書く。これは当たり前。
- 外部から来た文字列は、Perlに取り込んですぐ、decode()して内部表現化するべき。内部表現は「UTF8フラグをつける」とも呼ばれているが、UTF8とは別物。 http://blog.livedoor.jp/dankogai/archives/51224106.html http://blog.livedoor.jp/dankogai/archives/51031595.html
- decode()する方法としては、binmodeでSTDIN,STDOUTを自動で内部表現化とかopenでファイル読み込みを自動で内部表現化とか、 decode_utf8()等がある。 http://blog.livedoor.jp/dankogai/archives/51290188.html
- 外部に出す文字列は、出す直前にencode()してバイト列にする。しないで内部表現のままprintすると「Wide character in print ...」というエラーになる。 http://www.rwds.net/kuroita/program/Perl_unicode.html
- Perlソースコード内に書く文字列(literal)は use utf8と書けば内部表現化されるらしい。 http://d.hatena.ne.jp/shidho/20090406/p1
PerlでUTF-8を扱うサンプルソース
STDINから読み込んだUTF8データを、そのまま出力するのは、これでよい。
#!/usr/bin/perl use utf8; use Encode; my $content = ''; while(<>){ $line = decode_utf8($_); $content .= $line; } print encode_utf8($content);
print encode_utf8($content);
の代わりに
print encode('cp932', $content);
と書く。
しかしこれでは、$contentに出力文字をため込んで、一気に吐き出す方法である。
その都度printするごとに print encode_utf8() と書くのも面倒だ。
都度printするなら、以下のようにするほうが簡単。
use utf8; use Encode; use open IN => ':encoding(cp932)'; #input: ShiftJIS->Internal use open OUT => ':utf8'; #output: Internal->UTF-8 use open ':std'; # STDIN, STDOUT, STDERR while(<>){ print; }
IN,OUTともに UTF-8文字列なら、
use open IN => ':encoding(cp932)'; use open OUT => ':utf8';
の代わりに
use open ':utf8';
でよい。
PerlでOne-Liner
しかしワンライナーで
$ perl -pe "tr/ぁ-ん/ァ-ン/;" < ruby2.txt
と書きたいだけなのに、UTF-8のためだけに
$ perl -pe "use utf8; use open ':utf8'; use open ':std'; tr/ぁ-ん/ァ-ン/;" < ruby2.txt
と書きたくはない。以下の例はperl 5.10で実行した。
PerlはUTF-8時代にはone-linerに、あまり向いてないとわかった。
1. perl -u とするか、ソースコードに use utf8; と書けば、「文字列操作関数(length() とか正規表現とか)」がUTF-8対応版の動作をする。書かなければ 文字列操作関数は、文字列をバイト列として扱う。
2. 文字列はバイト列のまま。リテラル(ソースコード内に書かれた文字列)は、ソースコードがShiftJISで書かれてればShiftJISバイト列。ソースコードがUTF-8ならUTF-8バイト列。STDINは、shellがUTF-8環境ならUTF-8バイト列としてが入ってくる。shellがCP932なら(gnupackのCygwinはコレ)、CP932バイト列として入ってくる。つまり文字列には何もしない。
3. ShiftJIS文字列はそのままでは文字列操作関数で正しく扱えない。なので、文字列操作関数に食わせる前に各自で、 $utf8txt = $shiftjistxt.enc('shiftjis', 'utf8'); とかでUTF-8バイト列に変換する必要がある。Shift_JIS出力したいなら、出力前にShiftJISバイト列に変換してから出力すればいい。
これだけなら、簡単に覚えられて、簡単にPerlでUTF-8を扱えたのではと思う。One-Linerだって -u つければいいだけなんだからラクだった。
というか、PHPはそんな感じになっている。preg_replace()関数に u オプションをつければUTF8対応の動作になる等。