perlで扱うutf8フラグやutf-8-macの小ネタ

perlのスクリプトでmacの日本語ファイル名がマッチしない、という話題がtwitterに上がっていた。ネタ的に面白そうなので自分でも試してみたところ、macの日本語ファイル名が正規表現で一致しないことに気づいて、ちょっと吃驚。
フォルダには
・test.txt
・あいうえお
・ペダル
・memo.txt
など、アルファベットのファイルや日本語ファイル・フォルダが雑多に入っている。
この中から「あいうえお」と「ペダル」だけを検索で引っ張り出したい。というのが以下のスクリプト。
use strict;
use utf8;
use Encode;
binmode STDOUT=>’utf8’;
opendir(DIR, ’.’) || die; my @files = grep(!/^\./, readdir(DIR)); closedir(DIR);
foreach (@files){
my $f = Encode::decode(’utf8’, $_);
if( $f =~ m!あいうえお! ){
print $f , " <--- hit aiueo !! \n";
}
elsif($f =~ m!ペダル!){
print $f, " <--- hit pedaru !! \n";
}
}
ところが、これでヒットするのは「あいうえお」だけで、「ペダル」はヒットしない。
twitterでの流れを追うとどうやらmacのファイルシステムが(日本語ファイル名に)使うのは NFD という形式らしい(Normalization Form Canonical Decomposition)。これをperlで判定するためには NFC という形式に変換してやる必要がある(Normalization Form Canonical Composition)
NFDとNFCで問題になるのが。
・濁音などがはいると文字コードが違ってくる。
・macの場合はさらに一般的なNFDではなくて、utf-8-mac というちょっと特殊なもの。
『Geekのメモ: Mac OS Xのファイルシステムの文字コード』
↑こちらのページが具体的でわかりやすかった(感謝!)
お馴染みの Data::Dumper で読み込んだファイル名と、スクリプト内の文字を比べると、文字コードが違っていた。まさに上記した utf-8-mac の問題。「あいうえお」は濁音が混じってないので文字コードは同じだけど、「ペダル」は濁音部分で文字コードが違っていた・文字長が増えていた。
上記サイトを参考に、CPANから Text::Iconv をダウンロード、インストールしてテストスクリプト。
1)ファイル名を utf-8-mac から、utf-8に変換する。
2)変換したファイル名にutf8フラグをつける。
use Text::Iconv;
binmode STDOUT=>’utf8’;
opendir(DIR, ’.’) || die; my @files = grep(!/^\./, readdir(DIR)); closedir(DIR);
my $h; my $n = 1;
foreach (@files){
my $f = Text::Iconv->new(’UTF-8-Mac’, ’UTF-8’)->convert($_);
$f = Encode::decode(’utf8’, $f);
$h->{$f} = $n; ++$n;
}
foreach (keys %{$h}){ printf qq{%s ::: %s\n}, $_, $h->{$_}; }
print $h->{’ペダル’} ."\n";
↑で、無事確認できた。
ついでに。utf8フラグのおさらい。
文字コードのutf8とperl のutf8フラグは別モノ。
perlがスクリプトで内部処理するのにつけるのがutf8フラグということ…って、素人のわたしはよくわかってないので検証。
use strict;
use utf8;
use Encode;
binmode STDOUT=>’utf8’;
my $v = ’弱虫ペダル’;
while(<>){
if( $_ =~ m!$v! ){ print $_; }
my $line = Encode::decode(’ENCODE-NAME’,$_);
if( $line =~ m!$v!){ print $_; }
}
コマンドラインで引数に渡したファイルを読み込んで、「弱虫ペダル」を含んだ行を出力(プリント)するだけの単純なスクリプト。
・スクリプト自体は文字コード、utf8で書かれている。
・「弱虫ペダル」は文字コードがutf8で、 use utf8 によって、perl の utf8フラグがついている。
・最初の if 文は、utf8フラグのついていないテキストと、utf8フラグのついた「弱虫ペダル」の比較なのでヒットしない。
・Encode::decode で読み込んだファイルのテキストにutf8フラグをつける。
ENCODE-NAMEはファイルの文字コード。shiftjis、 utf8など。
・次の if 文は utf8フラグのついているテキストと、utf8フラグのついた「弱虫ペダル」の比較なので文字コードが * shiftjisだろうがutf8だろうが * ヒットする。
・スクリプトの文字コードはutf8でも、use utf8をつけない場合(スクリプトにutf8フラグがつかない場合)
・「弱虫ペダル」は文字コードがutf8で、perl の utf8フラグはついていない。
・最初の if 文では入力ファイルの文字コードが utf8 のものだけがヒットする。
・Encode::decode で読み込んだファイルのテキストに utf8 フラグをつける。
・次の if 文は utf8 フラグがついているテキストと、utf8フラグのついていない「弱虫ペダル」の比較なのでどれもヒットしない。
perl の utf8 フラグをつけることによって、スクリプトや、入力されるテキストの文字コードは何でも構わない、という超便利なフラグ(decodeで正しい文字コードを指定する必要はあるけど)
perlのutf8フラグネタでは以前 「utf8移行と自分メモその2」 こっちにも覚え書き(utf8フラグをつけたスクリプトは、変数に日本語が使える、というネタはこちら)
encodeネタでは変換に失敗する文字をチェックするのにencodeの第3引数に、というネタも 「メモ encodeで変換失敗を第三引数でフォロー」 …ほんと、なんちゅーかほんちゅーか。面倒くさい。
https://twitter.com/JunTajima/status/422190121455063040
話の発端からtwitterを追ってみると、utf16だったことが原因だったということでした。
て、utf16使う状況があるのか!?参考になります。ううううむ。難物。

