|
http://www.pure.ne.jp/~learner/program/Perl_unicode.html Perl 5.8.x Unicode関連 目次 Perl 関連 ・Perl 変数について ・Perl Tips ・Perlオブジェクト指向プログラミング ・Perl 5.8.x Unicode 関連 ・Perl(とか)の書籍
Perl 5.8.x のUnicode 関連です。 正直、5.8.x は、ネタでしか使ってなかったので(ぉ、ちゃんといじったことがありませんでした。 使ってみると、よくわかんなくなったので、ちょっとまとめてみました。 今でもあんまりわかってないかもしれないので、内容は無保証です。 突っ込み歓迎。 Jcode、Encodeのメンテナの弾さんから、ご指摘いただいたので、色々手をいれました。 弾さんに感謝! ▲ to Top UTF8フラグ? Perl 5.8.x では、内部的には、文字列は Unicode で扱われます。 内部的に扱われている Unicode 文字列には、 UTF8フラグというものがつくみたいです。 UTF-8 フラグというものは、perldoc で説明があります。 perldoc perluniintro フラグが無効であれば、スカラ内のバイト列は、シングルバイトエンコーディングとして、 解釈されます。フラグが有効であれば、スカラ内のバイト列は、 (マルチバイトの、可変調の) UTF-8 でエンコードされたキャラクタのコードポイントとして、解釈されます。 だそうです。 ですが、外部から読み込んだ文字列や、ソースコードに書いた"ほげ"とかいう文字列は、 UTF8フラグがついていません(use encoding "..." しない場合)。 UTF8フラグがついているか、ついていないかは、utf8::is_utf8関数を使います。 なお、utf8::* な関数は、use しなくても使えます。use すると、別の意味になってしまうので、注意してください。 あと、別にUTF8の文字列がUTF8フラグのついた文字列というわけではありません。 下のコードをUTF8で書いたとしても... #!/usr/bin/perl print utf8::is_utf8("ほげ") ? 'UTF-8 Flag' : 'not UTF-8 Flag'; 実行結果は、 not UTF-8 Flag さっき書きましたが、use encoding を使うと、UTF8フラグがつきます(ソースコードが UTF8 なら、use utf8してもつきます)。 ※use encoding で指定した文字コードで、ソースコードを記述する必要があります。 #!/usr/bin/perl use encoding 'euc-jp'; print utf8::is_utf8("ほげ") ? 'UTF-8 Flag' : 'not UTF-8 Flag'; 実行結果は、 UTF-8 Flag ▲ to Top UTF8フラグとPerlIOレイヤ Perl5.8.xで理解しなければならないものとして、UTF8フラグと、もう一つPerlIOレイヤがあります。 PerlIOレイヤ自体は、下記のように入出力時になんらかの操作を行うためにあります。 ______ ________________ ______ | 入力 |======>| perl の処理 |======>| 出力 | |______| |________________| |______| 入力を操作 出力を操作 で、Unicode関連とPerlIOレイヤは関係が深いのです。 UTF8フラグのついた文字列というのはPerlの内部的なものであって、Perlの外に出すためのものではありません。 また、Perlの外からPerlの中に入ってくるものも、Perlの内部的なものとは違います。 すなわち、標準出力、ファイルへの出力は、Perlの内部的なものを出しては駄目で、 標準入力、ファイルからの入力では、Perlの内部的な文字列とは違うものが入ってきます。 デフォルトは入出力時に特に何もしないので、入力から入ったものはただのバイト列と解釈されますし、 出力に出す物は、そのままで、内部フォーマットならそうだし、内部フォーマットから変換されたものなら変換されたものが出ていきます。 この入出力時にPerlの内部的なフォーマットに文字列を変換したり、バイト列にしたりするためにPerlIOレイヤが利用されます。 ______ ________________ ______ | 入力 |======>| perl の処理 |======>| 出力 | |______| |________________| |______| UTF8 UTF8 Shift_JIS ===> 内部フォーマット ======> Shift_JIS euc-jp等 euc-jp等 以下、これを実現するためのやり方です。 open プラグマを使う openプラグマを使って、入出力のデフォルトのエンコーディングを指定することができます。 use open IN => ":utf8"; # 入力をUTF8とする use open OUT => ":utf8"; # 出力をUTF8とする use open IO => ":utf8"; # 入出力をUTF8とする(上の2つを同時に行う) use open ":utf8"; # 上と同じ use open IN => ":encoding(CHAR_SET)"; # 入力をCHAR_SETとする use open OUT => ":encoding(CHAR_SET)"; # 出力をCHAR_SETとする use open IO => ":encoding(CHAR_SET)"; # 入出力をCHAR_SETとする(上の2つを同時に行う) use open ":encoding(CHAR_SET)"; # 上と同じ CHAR_SETに指定できる物は、Encode::Supported にあります。 上記で、特徴的な IN, OUT, IO は下記の通りです。 IN .... 入力のためのレイヤ OUT ... 出力のためのレイヤ IO .... 両方のためのレイヤ 省略 ... IOと同じ 標準入力、標準出力、標準エラーについては、上記の方法では何も起きません。 上記を宣言した後に、下記の宣言を行う必要があります。 use open ":std"; これを宣言することで、前に宣言している宣言が標準入出力、 標準エラーにも影響を与えるようになります。 openを三つの引数で使う open を三つの引数で使い第二引数にPerlIOレイヤを指定することができます。 open IN, "<:encoding(euc-jp)", "hoge.txt"; open IN, "<:utf8", "hoge.txt"; このようにすると、euc-jp のファイルを読み込むと、<IN>からUTF8フラグのついた文字列を受け取ることができます。 この三つの引数でopenを使う場合、先に宣言したopen プラグマのデフォルトのレイヤは使われません。 use open ":utf8"; open IN, "<", "hoge.txt"; # 三つの引数なので、デフォルトの指定が無効。バイト列が入ってくる open IN, "<hoge.txt"; # こちらは有効。バイト列はUTF8フラグがついたものに変換される binmodeを使う bimodeでは、すでに開かれているファイルハンドルに対して、PerlIOレイヤを指定します。 binmode STDOUT, ":utf8"; binmode IN, ":encoding(euc-jp)"; ▲ to Top UTF8フラグのついた文字列を記述する UTF8フラグがついた文字列というのを、ソースコードに記述(ちょっと違うかも)することも出来ます。 use utf8 して、ソースコードを UTF8 で記述する。 一番単純といえば単純ですが、ソースコードをUTF8で記述し、use utf8 します。 #!/usr/bin/perl use utf8; my $alpha = "α"; # UTF8で書くこと! use utf8 をすることで、perl にソースコード中の文字列がUTF8で書かれていることを教えます。 このことにより、ソースコード中の文字列には、UTF8フラグがつきます(詳細はuse utf8;)。 use charnames qw(:full) して、文字の名前から文字を呼び出す。 #!/usr/bin/perl use charnames qw(:full); my $alpha = "?N{GREEK SMALL LETTER ALPHA}"; ?x{...} を使用する。 #!/usr/bin/perl my $alpha = "?x{3b1}"; chr() を使用する。 #!/usr/bin/perl my $alpha = chr(0x3b1); pack("U", ...) を使用する。 #!/usr/bin/perl my $alpha = pack("U", 0x3b1); 上記の文字は、すべて、αです。ただし、use charnames qw(:full) と、?x{...} は、コンパイル時に決まるので、 "?x{$num}" のように、中に変数を使うことは出来ません。 Wide character in print ... UTF8フラグがついた文字列を print しようとすると...。 #!/usr/bin/perl my $alpha = "?x{3b1}"; print $alpha; 次のような警告がでることがあります。 Wide character in print at text.pl line 5. Wide character というのは、下記のような文字列のことです。 ・UTF-8 flag がたっていること ・その文字が 0x100 以上であること Unicode で、0x100未満というのは、ASCII と、Latin-1 です。 ですから、UT8フラグがついた文字列でも、次のようなものは、警告はでません。 #!/usr/bin/perl use Encode; my $str = 'abcde'; $str = Encode::decode("ascii", $str); print utf8::is_utf8($str); # 1 print $str; # 警告なし Wide character な文字列はそのまま出力すると文句を言われるというわけです。 回避するには、PerlIOレイヤを使うか、UTF8フラグを落とします。 use Encode; my $alpha = "?x{3b1}"; print Encode::encode("utf8", "?x{3b1}"); # UTF8フラグを落す utf8::encode($alpha); # UTF8フラグを落す(Encode::encode_utf8 と同じだが、引数を変化させる) print $alpha; binmode(STDOUT, ":utf8"); # PerlIOレイヤ print "?x{3b1}"; ▲ to Top Encode Encode が出て来ましたので、よく使う関数を説明します。 Encode::encode my $non_wide_char = Encode::encode($charset, $wide_char); UTF8フラグつきの文字を、指定した文字コードに変換する(UTF8フラグを落す)。 Encode::decode my $wide_char = Encode::decode($non_wide_char_charset, $non_wide_char); UTF8フラグなしの文字を その文字の文字コードを指定して、 UTF8フラグを付けます。 Encode::from_to from_to($string, $from_char_charset, $to_char_set); UTF8フラグのない文字列($string)の文字コードの変換に用います。 UTF8フラグのある文字列は、Encode::encodeを使用します。 以下、例です。なお、script は、euc-jp で書かれています。 #!/usr/bin/perl use charnames qw/:full/; use Encode; my $x = "?N{GREEK SMALL LETTER ALPHA}"; print Encode::encode("euc-jp",$x);' #!/usr/bin/perl use Encode; my $x = "あ"; binmode(STDOUT, "utf8"); print Encode::decode("euc-jp",$x); #!/usr/bin/perl use Encode; my $x = "あ"; from_to($x,"euc-jp","utf8"); print $x ▲ to Top utf8::* 関数 最初の方でもかきましたが、utf8::* な関数は、use しなくても使えます。use すると、別の意味になってしまうので、注意してください。 utf8::is_utf8 my $unicode = "?x{0x3b1}"; print utf8::is_utf8($unicode); # 1 UTF8フラグがあれば、真を返します。 utf8::encode my $unicode = "?x{0x3b1}"; utf8::encode($unicode); UTF8フラグを落とします。Encode::encode_utf8 と同じですが、引数を変化させます。 utf8::decode #!/usr/bin/perl open IN, '<', 'unicode.txt'; while(my $row = <IN>){ utf8::decode($x); push(@row, $row); } UTF8フラグをつけます。Encode::decode_utf8 と同じですが、引数を変化させます。 ▲ to Top use utf8; use utf8; すると、どうなるのか? すでに例が出ていますが、もう少し詳しく書いてみます。 perldoc utf8 を引用します。 このプラグマは、そもそも、互換性のための工夫です。 Perl 5.6未満のバージョンでは、ソースコード中の恣意的なバイト列を許していました、 ですが、将来、ソーステキストに、UTF-8 エンコーディングを標準化したいと思っています。 Perl に script が UTF-8 で書かれているということを教えることの他に、このプラグマを使ってはいけません。 下に書かれている、utf8のユーティリティ関数は、それ自身の目的で有益です。 それらは、"プラグマ的な"効果の部分ではまったくありません。 utf8のユーティリティ関数というのは前述の通りです。プラグマ的な効果は、 書かれているように、PerlにscriptがUTF8で書かれていることを教えることです。実際の効果は、下記のようになります。 1.ソースコード中の文字列がUTF8になる。 2.マルチバイトの変数名なども使える。 具体的なコードは、下記のようになります(文字コードはUTF8で記述すること)。 #!/usr/bin/perl use utf8; my $alpha = "α"; binmode STDOUT => ":utf8"; printf "$alpha (UTF-8:%d)?n", utf8::is_utf8($alpha); ここで、binmode STDOUT => ":utf8"; していることに注意してください。 use encoding "utf8"; とは違い、use utf8 は、PerlIOレイヤには手が加えられませんので、自分で書く必要があります。 ソースコードの別の部分で use utf8 の効果を無くしたい場合は、no utf8 を書きます。 { no utf8; my $alpha = "α"; binmode STDOUT => ":raw"; printf "$alpha (UTF-8:%d)?n", utf8::is_utf8($alpha); } これで、スコープ内は、use utf8 の効果がなくなります。 binmode STDOUT => ":raw" しているのは、 前回、binmode STDOUT => ":utf8"; しているため、 UTF8フラグのない $alpha を出力するのに、":utf8" のPerlIOレイヤでは困るので、":raw" にします。 注意(?)として。 スクリプト内に8bitのバイト列がある(たとえば、文字列リテラルに、Latin-1がある)なら、 "use utf8" は、不幸をもたらすでしょう。バイト列は、ほとんどの場合、適切なUTF-8ではないからです。 UTF8じゃないのに、use utf8; しないでねってことなようです。もしも、UTF8じゃない文字列を書きたいなら、 前述の通り、"no utf8;"をそのスコープで書けばいいわけです。 UTF8でソースコードを記述したくないけれど、UTF8フラグはつけたいということなら、下記のようにします。 自動的に、8bitレガシーのバイト列をUTF-8にアップグレードしたいなら、 "encoding" プラグマを、このプラグマの代わりに使ってくだださい。 たとえば、例で使われているように、ISO 8859-1 (Latin-1) のバイト列を UTF-8に暗にアップグレードしたいなら、"chr()" と "?x{...}"を使います。 次のようにします: use encoding "latin-1";
my $c = chr(0xc4);
my $x = "?x{c5}";
迷っている場合こうしてください。"use encoding 'utf8';"は、"use utf8;" と、大差ありません。 これにより、UTF8で書かれていないソースコードでも、ソースコード中の文字列にUTF8フラグをつけることができます(詳しくは後述)。 ですが、弾さんによると、encoding プラグマはお手軽だけど副作用が大きいとのことです。 Perlの方向性としては、 ソースコードをUTF8で書き、use utf8 するというのがいいらしいです。 ▲ to Top use encoding; use encoding プラグマの説明を少しだけ。 use encoding "CHAR_SET"; これを指定すると次の効果があります。 1. 内部的にすべてのリテラルを CHAR_SET から、utf8(UTF8フラグつき)に変換する。 2. PerlIOレイヤの STDIN と、STDOUT を指定された CHAR_SET にする。 ソースコードの文字コードを指定することで、内部的に、全てのリテラルに対して、 Encode::decde("CHAR_SET", ...); みたいなことをして、UTF8フラグをつけている感じです。 STDIN で、CHAR_SETがくると考えて、Encode::decode("CHAR_SET", ...) して、 STDOUTのときに、Encode::encode("CHAR_SET", ...);をして、UTF8フラグを落としている感じです。 ですので、use encoding CHAR_SET しているときに、CHAR_SET以外の文字コードの文字列をSTDINから受け取ると、 変なことになります。 cat utf8.txt | perl -e 'use encoding "euc-jp";my $utf8 = <>;' こんな警告がでます。 euc-jp "?xE3" does not map to Unicode at -e line 1. euc-jp "?x81" does not map to Unicode at -e line 1. euc-jp "?x81" does not map to Unicode at -e line 1. euc-jp "?x92" does not map to Unicode at -e line 1. PerlIOレイヤの効果で入力時に、euc-jp を期待しているのに、実際に入ってきているのは UTF8 なので、 UTF8 を euc-jp だと解釈してしまって、そんな euc-jp(実際はUTF8) を Unicode にマッピングはできないよ、と文句を言うわけです。use encoding "CHAR_SET" するときは、入出力に気をつけてください。 use encoding "CHAR_SET" に、さらに、Filter => 1 をつけると、面白いことになります。 use encoding "CHAR_SET", Filter => 1; いわゆる、ネタができます(ぉ。$ほげ とか、CHAR_SET で指定した文字を、 変数や、関数や、ループの名前に出来ちゃったりします。 #!/usr/bin/perl use encoding 'euc-jp', Filter => 1; my $入力回数 = 3; my @入力された文字; while($入力回数--){ $| = 1; my $入力 = <>; chomp($入力); push(@入力された文字, $入力); print "後、" . $入力回数 . "回、入力してください。?n" if $入力回数; } print join("-", @入力された文字),"?n"; ▲ to Top use utf8; と use encoding; use utf8; と use encoding は、ソースコードの文字列を指定し、ソースコード中の文字列にUTF8フラグをたてるところは同じです。 違いは? use utf8; use encoding; ソースコードの文字列にUTF8フラグ たてる たてる PerlIOレイヤ なし STDIN,STDOUTが指定した文字コードに 変数名にマルチバイト 使える FILTER=>1 が必要 こんな感じになります。 Jcode から Encodeに Jcodeの、一般的な使われ方を、Encode でどうするか。 文字コード不明の外部ファイルを読み、euc-jp に変更 use Encode qw(from_to); use Encode::Guess qw/euc-jp shift-jis/; # Guess は、UTF8を必ず判別するとのこと open IN, 'FILE'; while(<IN>){ chomp; from_to($_, 'Guess', 'euc-jp'); push(@row, $_); } close IN; from_to の第2引数を、Guess にすると、use Encoding::Guess に与えた文字コードの判別を行います。 ですが、この方法は下記の欠点を持ちます。 1. Guessは文字列が短いと判別に失敗しやすい("euc-jp or shiftjis" みたいなのを返す)。 2. 下手すると、一行ごとに違う文字コードと判断される可能性がある。 3. 判別に失敗すると例外を吐いて死ぬ。 そのため、下記のように、全体を読み込んでから変換のほうがいいとのことです。 use Encode qw(from_to); use Encode::Guess qw/euc-jp shift-jis/; open IN, 'FILE'; my $content = join '', <IN>; close IN; from_to($content, 'Guess', 'euc-jp'); それでも、全体すら短い文字列で、Guess で死んでしまう可能性を考慮するなら、 下記のように自前で die するとか、適当な文字コードを想定するとか。 use Encode qw(from_to); use Encode::Guess qw/euc-jp shift-jis/; open IN, 'FILE'; my $content = join '', <IN>; close IN; my $guess = Encode::Guess::guess_encoding($content); unless(ref $guess){ # 判別失敗
## die('cannot guess:' . $guess); # エラーを出して dieするとか
## Encode::from_to($content, "utf8", "euc-jp") # UTF8と仮定してみるとか
}else{ # 判別成功
$contents = $guess->decode($content);
$contents = Encode::encode("euc-jp", $content);
} 上記の例のように、use Encode::Guess の引数に文字コードを与える代わりに、 Encode::Guess->set_suspect(@charset)で、@charset に判断する文字コードをいれることもできます。 Encode::Guess->set_suspects(qw/euc-jp shift-jis/); Encode::($contents, "Guess", 'euc-jp'); と色々書きましたが、最後に弾さんによる、Guessを使う場合の心得。 1. 出来れば使わない 2. 使う場合は、データ全部をいっぺんに渡す 3. Guessしなかった場合のエラー処理をきちんとする 他のモジュール経由で受け取った内容で、UTF8フラグ付きかどうか不明なのをeuc-jp に変更 #!/usr/bin/perl use Encode; use Encode::Guess; use Hoge; my $contents = Hoge->get_contents(); # Hoge が返す値がUTF8フラグ付きかどうか不明 unless(is_utf8($contents)){ # UTF8フラグ がない場合、decode して、UTF8フラグつきにする my $gussed_obj = Encode::Guess::guess_encoding($contents, qw/euc-jp shift-jis/); ref $guesed_obj or die $guessed_obj; # Guess できなかった場合 die $contents = $gussed_obj->decode($contents); } $contents = Encode::encode('euc-jp', $contents); print $contents; utf8::is_utf8 で、UTF8フラグがついているかどうか判別します。 UTF8フラグがない場合に、文字コードを guess_encoding で判別し、 decode して、$contents をUTF8フラグ付きにします。 その後は、UTF8フラグ付きの$contents を、euc-jp にEncodeします。 半角カナへの変換 変換テーブルは、Encode::JP::H2Z にありますので、それを利用します。 use Encode; use Encode::JP::H2Z; sub z2h{ my $string = shift;
if(utf8::is_utf8($string)){
$string = Encode::encode("euc-jp", $string);
Encode::JP::H2Z::z2h(?$string);
return Encode::decode("euc-jp", $string);
}else{
Encode::JP::H2Z::z2h(?$string);
return $string;
}
} Encode::JP::H2Z は、UTF8フラグ付きのは受け付けないので、 UTF8フラグ付き文字列が来たら、こちらでeuc-jpにエンコードしてやってから、z2hに渡します。 UTF8フラグなしの場合、$string をそのまま渡してます。 JEncodeってのがあるみたいなんで、そっち使うのも良いかもしれません。 ▲ to Top 情報源 参考になるところ。 perldoc perldoc unicodeintro(邦訳) ... perl の Unicode の手引。 perldoc utf8(邦訳) ... utf8 プラグマと、utf8::* 関数について。 perldoc encoding perldoc Encode(邦訳) perldoc Encode::Guess Web 日本語に絡むUnicodeブロックとスクリプト(正規表現) PerlのUnicode support NDO::WebLog? Perl 5.8 以降においての Unicode 文字列の扱い方 perl5.8のUnicodeサポート ▲ to Top Copyright?2002-2004, Ktat All rights reserved. |