入力は可能な限り早く decode すべき
最近、perl の文字の扱いではまることはなかったが久々にはまった。 単語リストが書かれた word.txt を読み込んで、もにょもにょやろうとしている下記プログラムのどこがダメか。
# test.pl use 5.14.0; use warnings; use Encode 'decode_utf8'; open my $fh, "<", "word.txt" or die "word.txt: $!"; my %word; while (my $line = <$fh>) { $line =~ s/\s+$//; # 行末の空白や改行を除去 $word{ decode_utf8 $line }++; } # do something with %word use Data::Dumper; print Dumper \%word;
word.txt が
> cat word.txt 駅 原因
のような場合、
> perl test.pl $VAR1 = { "\x{539f}\x{fffd}\x{fffd}" => 1, "\x{fffd}\x{fffd}" => 1 };
となる。ここで "\x{fffd}" は壊れた unicode 文字の置き換えとして使われる文字である。
なぜこうなってしまったかというと 「駅」の utf8 byte 列は "\xe9\xa7\x85" であり、
$line =~ s/\s$//
で "\x85" (NEXT LINE) が除去されてしまったから。
ちなみにこの挙動は perl のバージョンによって変わり、
のようだった。
( 5.12 はおそらくバグってる。cf: https://gist.github.com/shoichikaji/70b714c7a79b59897bd6 )
つまるところ、入力はできる限り早く decode するようにすれば、このようなことは起こらない。 今回の場合は open layer に指定するのが一番。
# test.pl use 5.14.0; use warnings; open my $fh, "<:encoding(UTF-8)", "word.txt" or die "word.txt: $!"; my %word; while (my $line = <$fh>) { $line =~ s/\s+$//; $word{ $line }++; }
SEE ALSO
( perl のバージョンをいろいろ変えてみるべし)