2009年06月08日 14:30 [Edit]

perl - use encoding; #は黒歴史

camel

ぎゃあぁぁ

length関数で文字列の長さを求める - perl初心者BLOG - Hatena::Group::Perl
日本語の文字数を正確に求めたい場合、use encodingを指定する

use encoding;は、jperlなど、かつて存在したL10Nされたperl用に書かれたレガシースクリプトを、モダンperlで動かすときのためのおまじないです。こういう目的で利用すべきではありません。

このあたりのことは、以前

でも書いたのですが、大事なことなのでまた書きます。


スクリプトはUTF-8で書き、use utf8;する

のがモダンPerlのあり方です。

#!/usr/bin/perl
use strict;
use warnings;
{
  my $string = "dankogai小飼弾";
  printf "length('%s') == %d\n", $string, length($string);
}
{
  use utf8;
  my $string = "dankogai小飼弾";
  printf "length('%s') == %d\n", $string, length($string);
}

見ての通り、utf8プラグマは lexical scope で効きます。encodingプラグマではこうは行きません。

Wide character in printという warning が出ていますが、これは「UTF-8として扱われている文字列を、バイトストリームとして扱っています」という警告です。これを消すためには、STDOUTがUTF-8ストリームであることを教える必要があります。

あと、utf8プラグマが効いている状態で、文字列が何文字かではなく文字列が何バイトかを知るためには、bytes::length()を使います。

以上をふまえたサンプルが以下です。

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use bytes (); # () をお忘れなく

my $string = "dankogai小飼弾";
binmode STDOUT, ':utf8'; # STDOUTはUTF-8ストリーム
printf "length('%s') == %d\n", $string, length($string);
printf "bytes::length('%s') == %d\n", $string, bytes::length($string);

UTF-8以外の文字コードは、外部化するかバイト列として表現した上でEncodeで処理する

基本的なやり方は以前書いたとおりです。

どうしても外部化したくない場合には、明示的にバイト列として表現しましょう。以下はその例です。

#!/usr/bin/perl
use strict;
use warnings;
use Encode;

my $bytes = "\x95\x5C"; # 「表」をShift_JISで
my $string = decode "sjis", $bytes;
printf "length(\$string) == %d\n", length($string);
printf "length(\$bytes) == %d\n", length($bytes);

なぜそうすべきかというと、Shift_JISやBIG-5などでは、2バイト文字中にASCIIの記号が混じってしまうことがあるからです。以下のコードをShift_JISで保存して確認してみてください。

#!/usr/bin/perl
use strict;
use warnings;
use Encode;

my $bytes = "表";
my $string = decode "sjis", $bytes;
printf "length(\$string) == %d\n", length($string);
printf "length(\$bytes) == %d\n", length($bytes);

これは、表のSJIS表記、\x95\x5Cの二バイト目がバックスラッシュなので、"表""\x95\""と解釈されてしまうためです。EUCではこういう問題は発生しないのですが、こうした問題のわかりにくさと手間を考えたら、新たなスクリプトははじめからUTF-8で書くのが圧倒的に一番楽な方法ということになります。

use encoding;で新たなスクリプトを書かない

というわけで大事なことなので二度言うと、use encoding;はどうしても旧いスクリプトを動かしたいときだけに使い、新たなスクリプトはUTF-8で書くようにしましょう。幸せになれるかはさておき、不幸にあう確率はずっと下がります。

Dan the Maintainer Thereof


この記事へのトラックバックURL

この記事へのコメント
そうそう、読み込まれる側にも use utf8; を書いておく。
Posted by BLUEPIXY at 2009年06月08日 19:41
tmpu.txt : UTF-8
use utf8;
$main = "テスト";
1;

で解決しそうな気も。
Posted by 通りすがり2 at 2009年06月08日 17:17
requireで読み込んだUTF-8のファイルがメインのスクリプトでUTF-8と認識されないです。

tmpu.txt : UTF-8
$main = "テスト";
1;

main script : UTF-8
use utf8;
use strict;
use Encode;

our $main;
require "tmpu.txt";
$main = decode("utf8",$main); # Need to do this. Why?

print length($main);
Posted by hdk at 2009年06月08日 16:53
>use encoding;で新たなスクリプトを書かない

そこまでおっしゃるなら、ドキュメントにdeprecatedとかfor backward compatibilityとか馬鹿にでもわかるようなキーワードを入れた方がよろしいのでは?

Posted by 通りすがり at 2009年06月08日 15:25