Perl の多言語処理
Perl 5.8 以降,テキストの処理が UTF-8 (Unicode の変換形式のひとつ) を軸におこなわれるようになりました。Shift JIS や EUC-JP での処理が一般的な日本語を Perl で扱う場合,エンコードを意識したプログラミングをおこなうことが必要になります。具体的には,Perl でエンコードを意識する必要のあるのは以下の箇所です。
- スクリプト自体のエンコード
- ファイルの入出力
- 標準入出力
- 標準エラー出力
- データベース,CGIなどプロセス間のインターフェース
Perl 5.8 では内部処理が UTF-8 で行われますが,特にエンコードの指定がされない場合,テキストは文字単位ではなく,バイト単位で処理されます。バイト単位で Shift JIS などのテキストを処理することもある程度可能ですが,2バイトの文字が文字として認識されていませんから,正確に文字単位の処理をすることはできません。また,文字の区切りを理解しないで処理が行われる結果,意図しない処理結果やエラーが発生する場合があります。
大雑把に言って,テキストを正しく処理するためには,以下の3つの方策が考えられます。
- 特に何も指定しないかわりに,全てのテキストを UTF-8 で使う。
- 全てのテキストを統一したエンコードで処理する。
- テキストの処理をおこなうたびにエンコードを明示する。
- 基本的なエンコードを指定し,異なるエンコードを用いる場合に明示する。
このページでは,1. と 2. の方法を Shift JIS のテキストを処理する場合 を例に説明します。
Perl 5.8 では,特にエンコードの処理をしない場合,半角・全角の区別なく,テキストは全てバイト単位で処理されます。
そのため,エンコードを意識せずに Shift JIS のテキストを処理すると,Shift JIS 特有の制御文字を含む全角文字や半角カタカナが正しく解釈できず,処理結果がおかしくなることがあります。
- 元のテキスト:
日本代表の高原直康選手
- 処理後のテキスト:
日本代浮フ高原直康選手
上記のテキストの場合,Shift JIS の「表」 (コード 955C) が \ と同じ 5C というコードを含むため,予期しない結果になってしまっています。(このような文字化けの現象は従来の非日本語対応 Perl と同じですから,従来どおり 「日本代\表\の」という形での場当たり的なエスケープが必要になります。)
このような予期せぬエラーを避けるには,スクリプトを含め全てのテキストを暗黙的に UTF-8 や EUC-JP などのより安全なエンコードで作成・処理することが考えられます。ただし,この場合も複数バイトからなる文字を1文字とは認識していませんから,Shift JIS と同様,文字数を数えたり,文字単位で処理をさせることは困難です。
なお,ASCII の基本英数字・記号のみのテキストは従来どおり処理されるので,エンコードの指定なしで処理しても問題は起こりません。
全てのテキストを Shift JISで処理するには,Perl内部で Shift JIS のテキストを Unicode (UTF8) に変換させます。そこで,以下のようにencoding
プラグマで標準入出力,標準エラー出力のエンコードを記述します。shiftjis
のshiftとjisの間にはスペースが入りませんので気をつけてください。
use encoding "shiftjis";
binmode STDERR, ":encoding(shiftjis)";
1. の指定だけでは標準エラー出力のエンコードは変わりませんので,2. が必要になります (2. がないと,エラーは UTF-8 に変換されて表示されます)。日本語 EUC-JP を指定する場合には,"euc-jp"
,Unicode の変換形式 UTF-8 を使う場合には "utf8"
(つづりに注意) で shiftjis
を置き換えます (大文字・小文字は区別されません。なお,shiftjis
には sjis
という略式表現や shift_jis
といった別名も使えます。)。
なお,Perl 5.8.0 では Shift JIS の2バイト文字の一部が正しく処理できないバグがありましたが 5.8.1 以降では修正されています。
encoding プラグマを利用する場合,スクリプト,標準入出力などのデータが,全て指定されたエンコードで処理されます。
したがって,use encoding "shiftjis"
を利用する際,なんらかの理由で Shift JIS 以外のテキストが混じると,以下のようなエラーが出たり,文字化けが起こったりします。
Unrecognized character \xXX at [ファイル名] line N.
標準入出力,標準エラー出力以外のデータについては,Encode モジュールの encode, decode 関数を使い,個別にエンコードを指定する必要があります。これは特にファイルを読み込む場合に重要な手順となります (上記 encoding プラグマではエンコードは設定されません)。
以下のスクリプト encodeOpen.pl は decode 関数で入力ファイルの読み込み時にデータのエンコードを指定する例です (スクリプトと同じフォルダに sjis.txt
という Shift JIS で書かれたファイルがあることとします):
use encoding "shiftjis";
binmode STDERR, ":encoding(shiftjis)";
use Encode 'decode';
$inputtext = 'sjis.txt';
# 入力テキストは Shift JIS
open (IN, '<', $inputtext);
while (<IN>) {
# 標準入力 <>
ではないので注意!
$line = decode('shiftjis', $_);
# ファイルからの入力は Encode::decode
関数で個別にエンコードを指定する。ここでは shiftjis
を指定しているが,入力ファイルのエンコードにあわせ,euc-jp
なども指定可能。
print $line;
# ここで出力される内容は標準出力なので encoding プラグマにより Shift JIS に自動変換される。
}
close (IN);
以下のスクリプト encodeOpenWrite.pl は decode 関数で入力ファイルの読み込み時にデータのエンコードを指定し,encode 関数で出力データを指定したファイルに書き出す際にデータのエンコードを指定する例です:
use encoding "shiftjis";
binmode STDERR, ":encoding(shiftjis)";
use Encode 'decode', 'encode';
# use Encode qw/encode decode/;
とqw表記すると引用符やカンマを省略できる。
$inputtext = 'sjis_in.txt';
# 入力元のテキストは Shift JIS
$outputtext = 'sjis_out.txt';
# 出力先のテキストは Shift JIS
open (IN, '<', $inputtext);
open (OUT, '>', $outputtext);
$i = 0;
# カウンター
while (<IN>) {
# 標準入力ではないので注意!
$i++;
# カウンターを1つ増
$line = decode('shiftjis', $_);
# ファイルからの入力は Encode::decode
関数で個別にエンコードを指定する。ここでは shiftjis
を指定しているが,入力ファイルのエンコードにあわせ,euc-jp
なども指定可能。
$line = encode('shiftjis', $i . "\t" . $line);
# 出力するデータを指定し, Encode::encode
関数により Shift JIS に変換する。euc-jp, utf8 なども指定可能。
print OUT $line;
}
close (IN);
close (OUT);
decode
関数と decode
関数は,encoding
プラグマでエンコードを指定してあれば標準出入力には不要です。標準出入力のエンコードをこれらの関数で変更すると,2重に変換が行われる結果になり,以下のようなエラーが出たり,文字化けや予期せぬ出力の原因になったりします。
- decode 関数を標準入力に誤適用した場合のエラーの例:
Wide character in subroutine entry at ... Encode.pm line X, <> line N.
- encode 関数を標準出力に誤適用した場合のエラーの例:
"\x{nnnn}" does not map to encode at filename.pl line X, <> N.
なお,標準入力としてファイルからデータを読み込むにはリダイレクト <
を使います。この場合,encoding プラグマが指定されていれば,データは自動的に指定されたエンコードに変換されます。ところが,リダイレクトを使わず,スクリプト名のすぐ後にファイル名をそのまま続ける場合はファイルは標準入力ではなく,コマンドラインで指定されたファイルを読み込むための ARGV
という特殊なファイルハンドルを使って読み込まれます。標準入力も ARGV も,空のファイルハンドル <>
を使ってデータを読むのですが,ARGV には encoding プラグマによるエンコードの指定は効力をもちませんから厄介です:この場合は <>
から読み込んだデータを decode
関数で変換する必要があります。
perl スクリプト名 < 入力ファイル名
(標準入力 = encoding プラグマでエンコードが決定される)
perl スクリプト名 入力ファイル名
(空のファイルハンドル <>
で読み込んだデータであっても,ファイルハンドル ARGV には encoding プラグマは効果をもたない:decode 関数でUnicodeに変換する必要あり)
リダイレクトのあるなしで大きな違いが出るのですから,かなりまぎらわしいですね。
Perl 5.8.x で利用可能なエンコードの名称は,スクリプト listEncodings.pl
のようにして調べることができます。ActivePerl 5.8.2 がサポートするエンコードは以下のようになっていました。
7bit-jis AdobeStandardEncoding AdobeSymbol AdobeZdingbat ascii ascii-ctrl big5-eten big5-hkscs cp1006 cp1026 cp1047 cp1250 cp1251 cp1252 cp1253 cp1254 cp1255 cp1256 cp1257 cp1258 cp37 cp424 cp437 cp500 cp737 cp775 cp850 cp852 cp855 cp856 cp857 cp860 cp861 cp862 cp863 cp864 cp865 cp866 cp869 cp874 cp875 cp932 cp936 cp949 cp950 dingbats euc-cn euc-jp euc-kr gb12345-raw gb2312-raw gsm0338 hp-roman8 hz iso-2022-jp iso-2022-jp-1 iso-2022-kr iso-8859-1 iso-8859-10 iso-8859-11 iso-8859-13 iso-8859-14 iso-8859-15 iso-8859-16 iso-8859-2 iso-8859-3 iso-8859-4 iso-8859-5 iso-8859-6 iso-8859-7 iso-8859-8 iso-8859-9 iso-ir-165 jis0201-raw jis0208-raw jis0212-raw johab koi8-f koi8-r koi8-u ksc5601-raw MacArabic MacCentralEurRoman MacChineseSimp MacChineseTrad MacCroatian MacCyrillic MacDingbats MacFarsi MacGreek MacHebrew MacIcelandic MacJapanese MacKorean MacRoman MacRomanian MacRumanian MacSami MacSymbol MacThai MacTurkish MacUkrainian MIME-B MIME-Header MIME-Header-ISO_2022_JP MIME-Q nextstep null posix-bc shiftjis symbol UCS-2BE UCS-2LE UTF-16 UTF-16BE UTF-16LE UTF-32 UTF-32BE UTF-32LE UTF-7 utf-8-strict utf8 viscii
日本語は euc-jp, shiftjis 以外のエンコードとして,cp932 (Windows の日本語コードページ) や iso-2022-jp も使えることがわかります。ただし,前者に関して,cp932 に含まれている髙橋の「はしご高 (\x{9AD9})」や① (\x{2460}) ㈱ (\x{3231}) などの合成文字のような「機種依存文字」は現在の Perl ではサポートされておらず,標準的なモジュールだけでは正しく処理することはできません。(内部コードへの変換の際に削除されたり,他のエンコードへの変換の際にエラーが出たりします。)