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(とか)の書籍

  • > 趣旨と注意書き
  • > UTF8フラグ?
  • > UTF8フラグとPerlIOレイヤ
  • > UTF8フラグのついた文字列を記述する
  • > Wide character in print ...
  • > Encode
  • > utf8::*
  • > use utf8;
  • > use encoding;
  • > use UTF8 と use encoding
  • > JcodeからEncodeへ
  • > 情報源

    - モドル 趣旨と注意書き

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.


Last-modified: 2013-03-01 (金) 11:07:07 (2389d)