ブログトップ 記事一覧 ログイン 無料ブログ開設

サンプルコードによるPerl入門 - ゼロからPerlを始めてプロの技術を学ぼう

ゼロから初めて、プロの実力がどんどん身につく、サンプルが豊富なPerlの入門サイト
テキスト処理ならPerlが最適。if文,for文,配列,ハッシュ,正規表現を覚えれば、ログ解析も自由自在。Webサイトを作って独自のサービスの展開も! 関数とモジュールを覚えれば業務効率が劇的に改善。初級から上級まで、Perlのすべてが学べます。情報を得るだけではなく、プロとして生きていくために、プログラミングの確かな実力を「サンプルコードによるPerl入門」で身に着けてみませんか。
Twitterフォローで、Perlの最新情報と学び方解説

2009-11-18

Encode - 日本語などのマルチバイト文字列を適切に処理する / Perlモジュール徹底解説

 日本語などのマルチバイト文字をPerlで適切に扱うにはEncodeモジュールを使用します。次の3つのことを覚えておけば多くの場合適切に日本語を扱うことができます。

  1. 外部から入力された文字列はEncodeモジュールのdecode関数でデコードする
  2. 外部へ出力する文字列はEncodeモジュールのencode関数でエンコードする
  3. ソースコードはUTF-8で保存しutf8プラグマを有効にする

この解説での用語

 この解説では外部から入力された文字列のことを「バイト文字列」と呼ぶことにします。Perlの内部表現に変換された文字列を「内部文字列」と呼ぶことにします。また「バイト文字列」が特定の文字コードで記述されている場合は「UTF-8バイト文字列」「Shift_JISバイト文字列」などと呼ぶことにします。

 プログラミングで日本語を扱う場合にその文字列がバイト文字列であるのか内部文字列であるのかを明確に区別する必要があります。内部文字列とバイト文字列の変換に関してはプログラムが責任を持つというのがPerlで多言語を扱うときのルールです。この方法は少し手間がかかりますが、上記の3つのルールを覚えておけばよいだけなので仕組みとしてはとてもシンプルです。

外部から入力された文字列は必ずデコードする

 外部から入力された文字列は必ずEncodeモジュールのdecode関数を使用してデコードします。デコードとは「バイト文字列」を「内部文字列」に変換する処理のことをいいます。マルチバイト文字列を扱う場合はdecode関数を使用して必ず内部文字列に変換します。

 decode関数の第1引数にはバイト文字列の文字コード、第2引数にはバイト文字列を指定します。戻り値は内部文字列になります。

use Encode 'decode';

# 外部からの入力(コマンドライン引数)
my $str = shift;

# バイト文字列(外部からの入力)を内部文字列に変換($strがUTF-8の場合)
$str = decode('UTF-8', $str);

# バイト文字列(外部からの入力)を内部文字列に変換($strがShift_JISの場合)
$str = decode('Shift_JIS', $str);

 外部から入力される文字列というのは「コマンドライン引数」「ファイル」「標準入力」「環境変数」などです。外部からの入力はすべてバイト文字列であると考えてください。

$str = decode('UTF-8', $str);

 を図式的に表現すると以下のようになります。

                「UTF-8バイト文字列」を「内部文字列」に変換
UTF-8バイト文字列                 --->                 内部文字列

 decode関数の第1引数の意味は勘違いしやすい部分です。バイト文字列が実際にはどの文字コードでエンコーディングされているかを指定します。

外部へ出力する文字列は必ずエンコードすること

 外部へ出力する文字列はEncodeモジュールのencode関数を使用してエンコードします。エンコードとは「内部文字列」から「バイト文字列」に変換する処理のことをいいます。

 encode関数の第1引数にはどの文字コードに変換するかを指定します。第2引数には内部文字列を指定します。

use Encode 'encode';

# 内部文字列をUTF-8バイト文字列に変換する場合
$str = encode('UTF-8', $str);

# 内部文字列をShift_JISバイト文字列に変換する場合
$str = encode('Shift_JIS', $str);

 decode関数を使用して内部文字列に変換した文字列を出力する場合は必ずencode関数でバイト文字列に戻す必要があります。どのタイミングでencode関数を使って変換を行うかといえば出力するぎりぎりのところです。プログラムの中ではできるだけ遅いタイミングまで内部文字列を保つようにします。

$str = encode('UTF-8', $str);

を図式的に表現すると以下のようになります。

                「内部文字列」を「UTF-8バイト文字列」に変換
内部文字列                 --->                 UTF-8バイト文字列

ソースコードはUTF-8で保存しutf8プラグマを有効にすること

 マルチバイト文字を扱う上でもうひとつ気にする必要があるのはソースコードの中に記述する文字列です。もし日本語などのマルチバイト文字をソースコードの中で記述する必要があるならソースコードはUTF-8で保存してください。その上でutf8プラグマを有効にします。

# ソースコードはUTF-8として保存
# utf8プラグマを有効にする
use utf8;

my $str = "日本語などを書く。";

 ソースコードの中に書かれた文字列のことを文字列リテラルと呼びます。覚えておいて欲しいことは文字列リテラルの文字コードはソースコードの文字コードと同じになります。ソースコードをUTF-8で保存すれば文字列リテラルはUTF-8バイト文字列となり、Shift_JISで保存すれば文字列リテラルはShift_JISバイト文字列となります。

 特定の文字コードで保存する方法はエディタによって異なります。参考にWindowsのメモ帳の場合を説明します。これがわかれば他のテキストエディタでも理解できると思います。

 「ファイル」→「名前をつけて保存」→「文字コード」→「UTF-8」を選択。

 utf8プラグマというのはソースコードに書かれたUTF-8バイト文字列を内部文字列に変換する効果があります。つまりソースコードをUTF-8で保存してutf8プラグマを有効にすれば文字列リテラルは内部文字列に変換されます。

 utf8プラグマの効果を図式的に表すと次のようになります。decode関数と効果が良く似ています。

                                use utf8
UTF-8バイト文字列                 --->                 内部文字列

 最初に述べた次の3つのことを守りさえすれば文字コードで悩むことはだいぶ少なくなると思います。文字コードで悩んだらまずこの原則に戻ってみてください。

内部文字列に変換することの意味

 それでは実際に内部文字列に変換した効果をみてみましょう。Perlが正しく文字列を扱えていることがわかると思います。ソースコードはUTF-8で保存してください。UTF-8で保存されていない場合は「Malformed UTF-8 character」などという警告がでます。

 解説はUTF-8の場合で行いますがWindowsの場合はencode関数やdecode関数にcp932を指定する必要があります。cp932というのはWindowsの文字コードである「Windows-31J」を表すものです。コマンドライン引数には「これは日本語です。」という文字列を渡してみてください。

# 内部文字列に変換した効果を試す
use strict;
use warnings;

use utf8;
use Encode qw/encode decode/;

# コマンドライン引数(UTF-8バイト文字列)
my $str1 = shift;

# UTF-8バイト文字列を内部文字列にデコード
$str1 = decode('UTF-8', $str1);

# 文字列リテラル (utf8プラグマが有効なので内部文字列になる)
my $str2 = "日本語";

# 内部文字列に変換すると正しく文字を数えることができる
print length $str2, "\n"; # 3

# 内部文字列どうしであれば正しく正規表現を使用できる
if ($str1 =~ /$str2/) {
  print "Match!\n";
}

# 出力する直前に内部文字列をバイト文字列にエンコード
$str1 = encode('UTF-8', $str1);
$str2 = encode('UTF-8', $str2);

print "'$str1' is match '$str2'\n";

文字コードの変換

 文字コードの変換処理はPerlではdecode関数とencode関数を組み合わせて次のようにします。いったん内部文字列に変換する必要があります。次の例はUTF-8バイト文字列をShift_JISバイト文字列に変換する例です。

# UTF-8バイト文字列を内部文字列に変換
$str = decode('UTF-8', $str);

# 内部文字列をShift_JISバイト文字列に変換
$str = encode('Shift_JIS', $str);

 図式すると次のようになります。

UTF-8バイト文字列  -->  内部文字列  -->  Shift_JISバイト文字列

 これは少々面倒なので、from_toという関数が用意されています。第1引数はバイト文字列、第2引数は変換前の文字コード、第3引数は変換後の文字コードです。enocdeやdecode関数とは異なり第1引数に指定したバイト文字列自体が変換されることに注意してください。

# 文字コードの変換
use Encode 'from_to';

# $str自体が変換される
from_to($str, 'UTF-8', 'Shift_JIS');

ファイル名として指定する文字列

 openやunlinkなどの関数にファイル名を指定するときは、OSのバイト文字列に変換して指定する必要があります。

use strict;
use warnings;
use utf8;
use Encode 'encode';

my $file = 'あいう.txt';
open my $fh, '<', encode('cp932', $file)
  or die "Can't open " . encode('cp932', $file) . ":$!";

 ファイル名を扱う関数に渡す直前でエンコードするのがポイントです。これはファイル名を扱う関数をたくさん扱う場合は少し面倒ですので、ファイル名をOSの文字コードに変換する関数を作っておくと便利です。

 またエラーメッセージを自動OSの文字コードに変換するためにbinmode関数を使って標準エラー出力のエンコーディングを指定しておくと便利です。

use warnings;
use utf8;

use Encode qw/encode decode/;
my $enc = 'cp932';
binmode STDERR, ":encoding($enc)";
sub d($) { decode($enc, shift) }
sub e($) { encode($enc, shift) }

my $file = 'あいう.txt';
open my $fh, '<', e$file
  or die qq/Can't open "$file": $!/;

 これできわめて簡潔にPerlの文字コードを扱うことができるようになりました。

文字列に関するその他の注意点

内部文字列とバイト文字列の連結は避ける

 内部文字列とバイト文字列を連結は避けましょう。この場合はバイト文字列は自動的に内部文字列へと変換されるのですが、その変換は文字化けの原因になります。すべて内部文字列に変換してから連結を行いましょう。

内部文字列かバイト文字列かの識別は正確にはできない

 Perlではプログラムの中でその文字列がバイト文字列なのか内部文字列なのかを正確に識別する方法がありません。内部文字列かバイト文字列かを識別するためにutf8::is_utf8メソッドを使用することはできません。utf8::is_utf8メソッドはUTF8フラグが立っているかどうかを判別できるだけで、内部文字列かバイト文字列かを判定することはできないのです。

 ですから、おそらくこうなっているだろうという推測には利用できますが、内部文字列かバイト文字列かをプログラム内で確定させるための判定には使えませんので注意してください。UTF8フラグが立っていた場合は必ず内部文字列です。UTF8フラグが立っていない場合は内部文字列かバイト文字列です。

モジュールとの連携

 モジュールのある関数が引数として内部文字列を受け取るかバイト文字列を受け取るかはモジュールの作成者の意図によります。関数の戻り値についても同じです。バイト文字列を返すか、内部文字列を返すかは作成者の意図によります。必ずドキュメントを読んでください。

 これはPerlでプログラムをする上でプログラマが特に大変だと思う点のひとつだと思います。けれども単に否定せずに「後方互換性の維持」「シンプルな変換規則」「文字列の正確で適切な扱い」というその他の大きな恩恵にあずかっているということを思いだしてみてください。他の言語と比べてもPerlは文字列の扱いをかなりよくやっていると思います。

 その他のプログラミング言語の実装を見てもわかると思いますが、文字コードの扱いは何かとのトレードオフになっています。それくらい扱うのが難しい分野です。

内部文字列について

 内部文字列がどのようなものかについては意識しないほうがよいでしょう。プログラムを作成する上で意識する必要も特にありません。それでも知りたいという方は次の解説がわかりやすいです。


Perlモジュール徹底解説へ

hn0602hn0602 2009/11/05 03:12 この解説でEncodeがやっとわかった!!!
って感じです。

ありがとうございました。

perlcodesampleperlcodesample 2009/12/26 22:21 ありがとうございます。

akachochinakachochin 2010/05/10 23:49 akachochinです。
ご指摘ありがとうございます。やはり、自分が覚えてみたことは文章にして公開するのが吉ということがわかりました。

特にlatin-1は盲点でした。
それにしても、わかりやすいTechnical Sourceを教えていただき感謝です!

ぜひとも一緒に飲みたいものです(笑)

treeboatreeboa 2010/08/05 19:26 内部と外部わかった
読み込む奴は1回デコードするんだね!
ありがとうこれ最高!!!!!!!!

perl48perl48 2010/11/16 00:17 ご指摘ありがとうございました。akachochinさんと同じく、フワフワした部分を残しながらもアウトプットして良かったです。他のエントリも参考にさせていただきます!

Dr_RadialistDr_Radialist 2011/07/11 08:20 ありがとうございます。Perl 4を昔やっていたのですが、いきなりPerl 5.8になり、文字コードの件で混乱していました。ものすごく助かりました。

名無し名無し 2011/07/12 08:16 確か”UTF-8フラグ”でなく”UTF8フラグ”ですね。Perlではハイフンのありなしは区別されたと思います。

perlcodesampleperlcodesample 2011/07/13 13:49 UTF-8フラグをUTF8フラグに修正しました。

deepwhitedeepwhite 2011/08/09 13:09 入力も出力もPerlの関数や演算子など何らかの機能を使って行うわけだから
そのタイミングを捉えて、インタプリタが自動的にデコード・エンコードを
行ってくれるといいのに、と思ってた頃もありました。

でも全部バイナリで処理することでUTF8フラグを使わないで書く方法を最近
知ったので今はそうしてます。

perlcodesampleperlcodesample 2011/08/10 18:06 >deepwhiteさん
 その方法だと日本語に対して、正規表現や文字列の長さなどを取得関数が正しく実行できないので、まったくお勧めできない方法です。プログラムの中では内部文字列として持つということを守ったほうがよいです。

deepwhitedeepwhite 2011/09/01 13:49 バイナリと文字列を別のものだとするプロパガンダがありますが、
実際には同じものなので、区別せず扱った方が分かりやすいです。

プログラマとして思うのは、ソースコードは常に自ら(自分、自分たち)
がコントロールできるようにしておいたほうがよいってことです。
その意味で自分たち以外(部外者)に気遣ってソースを書くことはあり
ません。

UTF8フラグ問題というのは、脳内で生み出した幽霊と戦っているような
ものなので勝ち目がないのです。

perlcodesampleperlcodesample 2011/09/02 16:16 >deepwhiteさん
 同じではないです。Perlでは正規表現や文字列関数はバイナリに対しては正しく機能しないんです。部外者ではなく、Perlがバイナリと文字列を区別します。

deepwhitedeepwhite 2011/09/03 22:56 本当にそうですか?

JcodeJcode 2012/12/31 00:33 書籍拝見しました。
文字コードの判定をするには、Encode::Guessを使うのでしょうか。

特にフォームの入力データの処理は今風にはどんな感じでされているのでしょうか。

perlcodesampleperlcodesample 2013/01/04 15:16 JCodeさん

 フォームのデータ入力のときは、文字コードの判定は行いません。HTMLもソースコードもUTF-8で記述して、ソースコードには、use utf8プラグマを指定します。僕はMojoliciousというフレームワークを使っていますが、Mojoliciousを使うと、受け取った文字列は内部文字列に、出力される文字列UTF-8に自動的に変換してくれます。

おじさんおじさん 2014/10/22 13:57 【誤】utf8プラグマの効果を図式的に表すと次のようになります。encode関数と効果が良く似ています。
【正】utf8プラグマの効果を図式的に表すと次のようになります。decode関数と効果が良く似ています。
分かりづらいので間違えやすいのです。

perlcodesampleperlcodesample 2014/10/28 22:29 ありがとうございます! 大事なところが間違っていました。修正しました。

投稿したコメントは管理者が承認するまで公開されません。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証