おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

文字コード(3)

perlと文字コード

文字と文字コード

「文字コード(1)」で述べたように、PCで文字を扱うには、各文字に対応するコードを決めて、そのコードで文字を認識します。このように、文字にコードを対応させることを perl では、encode といいます。逆にコードから文字を対応させることを decode と言います。もちろん、この対応において、どの文字コードを使っているかははっきりさせなければなりません。
「A(半角のA)」 ->(asciiでencode)-> 16進数の「41」(以下これを 0x41 と書く)
「A(半角のA)」 ->(UTF-8でencode)-> 0x41 (asciiと同じ)
「日」 ->(UTF-8でencode)-> 0xE697A5
「日」 ->(euc-jpでencode)-> 0xC6FC
「日」 ->(shift_jisでencode)-> 0x93FA

0x41 ->(UTF-8でdecode)-> 「A(半角のA)」
0xE697A5 ->(UTF-8でdecode)-> 「日」
これを、perlのスクリプトでは次のように書きます。
use utf8;
use Encode;

...
...

$b = encode("utf8", "A"); # A を UTF-8 でエンコードして $b に代入
  # $b は1バイトの文字列で、その1バイトには0x41が入っている

$b2 = encode("utf8", "日"); # 日 を UTF-8 でエンコードして $b2 に代入
  # $b2 は3バイトの文字列で、その3バイトには0xE697A5が入っている。
  # $b2 は1バイトを単位とする長さが3の文字列と認識される。
  # つまり E6 97 A5 の3バイトはそれぞれ別々の1バイトずつの文字と認識されている。
  # このような1バイト単位の文字列をバイト文字列という。

$b3 = encode("euc-jp", "日"); # 日 を euc-jp でエンコード
$b4 = encode("shiftjis", "日"); # 日 を shift_jis でエンコード

$s = decode("utf8", $b); # $b を UTF-8 でデコードして $s に代入
$s2 = decode("utf8", $b2); # $b2 を UTF-8 でデコードして $s2 に代入
プログラムの経験がある人の中には、ここまでの説明が腑に落ちないという人もいると思います。というのは、「文字列」イコール「文字コードの列」であるから、エンコード前の文字列だって文字コードはあるはずだ、それはどうなっているのだろう、ということです。現時点では、perlの内部ではUTF-8を使って文字を表しているようです。しかし、perlの内部でどのように文字を扱かっているのかをプログラマーが知っている必要はありません。もしもperlの内部が別の文字コードを使っていたとしても、それはperl内部で正しく処理をしてくれるので、スクリプトを記述する上では知る必要がないのです。そして、上記の encode, decode はperlの内部で使う文字コードに関係なく正しく動作することをperlが保証しています。プログラマーとしては、perlの文字列は、純粋な文字列として(文字コードを無視して)扱えば良いのです。

また、perlの内部でUTF-8を用いて文字列を表していても、元の文字列とバイト文字列のperlの扱いは異なってきます。例えば"日"を考えてみましょう。perlの内部では UTF-8 を用いているので、"日"は0xE697A5の3バイトのコードの列である。しかし、"日"の方は、perlはUTF-8の文字コードで解釈するので、3バイトで1文字と認識される。文字の長さは1になる。他方、バイト文字列$b2の方は0xE697A5の3バイトのコードの列であるが、perlではUTF-8とは解釈しないので、1バイトずつ、3つの文字と認識する。

バイト文字列は、「文字列」とは言いますが、普通の文字コードの様にコードに文字が対応している訳ではなく、単なるデータの列にすぎません。

perlの入出力と文字列

perlは、外部とのデータのやりとりはすべてバイト単位で行います。日本語の文字などは、2バイト以上で表されますから、そのままでは外に出力できません。これをやると、perlはエラーの警告を出してきます。そこで、文字列はすべてエンコードしてバイト文字列にしてから出力します。

逆に入力データはすべてバイト単位であるので、それが日本語の文字列のような複数バイトの文字を含むものであれば、デコードしなければなりません。そして、外部からのデータがどのような文字コードなのかは、スクリプトが何らかの手段で知らなければなりません。あらかじめ分かっていれば簡単ですが、そうでなければ、入力データの中に文字コードを示すキーが入っているようなことが必要です。

サンプルプログラム

「日」をUTF-8、euc-jp、shift_jisで表したときに16進数のコードがどうなるかを表示するスクリプトです。エンコード、デコードがあちこちに出てきます。とくに出力前にはかならずエンコードする必要があり、うっかりしないよう注意が必要です。

なお、このプログラム(code.pl)は、UBUNTU (Linux、システムの文字コードは UTF-8)の上で動いています。端末を起動して、
$ perl code.pl
と打ち込み、動かします。Windowsなどの他のシステムだと、出力の文字コードをUTF-8ではなくて、cp932という文字コードになるかもしれません(未確認)。その場合、おそらく13行目の「$bch{"utf8"}」を「$bch{"shiftjis"}」に直せば動くのではないかと思います。なお、cp932はShift_JISをマイクロソフトが拡張したコード体系だそうです。
use utf8;
use Encode;


@ccode = ("utf8", "euc-jp", "shiftjis");

$ch = "日";

foreach $x (@ccode) {
  $bch{$x} = encode($x, $ch);
}

print $bch{"utf8"}."\n";

foreach $x (@ccode) {
  print "$x  ";
  &pr($bch{$x});
}

sub pr{
my $s = shift;
my @s = split(//,$s);
foreach $x (@s) {
  printf("%02X ", unpack("C",$x) );
}
print "\n";
}