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.