« Google Public DNSを使うと遅くなるかもしれない | トップページ | Perlのopendirは第2引数が0x5Cで終わるとエラーになる? »

2009年12月17日

WindowsのPerlで日本語パス名を処理すると0x5C文字でつまずく

仕事で必要性を感じてきたのでPerlを勉強している。普段使っているWindows PCでPerlスクリプトを書いていて、日本語パス名の処理でつまずいてしまった。シフトJISで0x5Cを2バイト目に持つ文字がディレクトリ名の末尾にあると、それ以下の階層をFile::Findで走査できない。「\」と同じ0x5Cを2バイト目に持つ文字は、英語版プログラムをシフトJIS環境で動作させたときに文字化けなどの誤動作を起こす代表的な文字のひとつである。

木本裕紀氏のサンプルコードを使って、再帰的にすべてのファイル名をフルパスで出力してみた。環境はWindows XP SP2とActivePerl 5.10.1.1006である。

まず、サンプルコードが内部で生成したテスト用のディレクトリ構造は次のように出力される。ディレクトリ名やファイル名は全て英数字だ。

dir0
dir0/top.txt
dir0/dir1
dir0/dir1/1.txt
dir0/dir1/dir1_1
dir0/dir1/dir1_1/1_1.txt
dir0/dir1/dir1_2
dir0/dir1/dir1_2/1_2.txt
dir0/dir2
dir0/dir2/2.txt

次に、dir1を「十分」に改名して同じ処理を実行する(ディレクトリ構造生成部分は削除。「十」はシフトJISコードで0x8F5C。

dir0
dir0/top.txt
dir0/dir2
dir0/dir2/2.txt
dir0/十分
dir0/十分/1.txt
dir0/十分/dir1_1
dir0/十分/dir1_1/1_1.txt
dir0/十分/dir1_2
dir0/十分/dir1_2/1_2.txt

これはうまくいった。次に「十分」を「何十」に改名してみる。

dir0
dir0/top.txt
dir0/何十
dir0/dir2
adir0/dir2/2.txt

「何十」ディレクトリ以下が出力されなくなってしまった。「申請表」でも同様だ。

dir0
dir0/top.txt
dir0/申請表
dir0/dir2
dir0/dir2/2.txt

もうひとつ下の階層で試してみた。dir1_1を「何十」に改名する。

dir0
dir0/top.txt
dir0/dir1
dir0/dir1/1.txt
dir0/dir1/何十
dir0/dir1/dir1_2
dir0/dir1/dir1_2/1_2.txt
dir0/dir2
dir0/dir2/2.txt

やはり「何十」ディレクトリ以下が出力されなくなった。NTFSはUnicodeでファイル名を保存しているはずなのに、シフトJIS特有の「0x5C」が顔を出してくる。WindowsカーネルからPerlスクリプトのどこかでシフトJISに変換されているのだろうか。

英語版Windows 2003でもテストしてみた。日本語を表示・入力できるように東アジア言語をインストールしてある。dir1を「何十」に改名した場合はこうなる。

dir_20080530_2464
dir_20080530_2464/top.txt
dir_20080530_2464/dir2
dir_20080530_2464/dir2/2.txt
dir_20080530_2464/148A~1
dir_20080530_2464/148A~1/1.txt
dir_20080530_2464/148A~1/dir1_1
dir_20080530_2464/148A~1/dir1_1/1_1.txt
dir_20080530_2464/148A~1/dir1_2
dir_20080530_2464/148A~1/dir1_2/1_2.txt

全階層を走査しているが、日本語が表示されない。「148A~1」はどこから来ているのだろうか。

同じことをCentOS 4.7(ロケールUTF-8)上のPerl 5.8.5でやってみると、どのパターンも正常に処理できる。

これはおそらく、perldoc perlunicodeが例外としてあげている部分に相当するのだろう。ファイル名はオペレーティングシステムやファイルシステムに依存する度合いが大きいから、Perlはバイト文字列として扱っている。

The following are such interfaces. For all of these interfaces Perl currently (as of 5.8.3) simply assumes byte strings both as arguments and results, or UTF-8 strings if the encoding pragma has been used.

ではどうすればよいかというと、perldoc perlunitutが書いているように、

・入力したバイト文字列をデコードする。
・Perlスクリプトで処理する。
・出力する前にエンコードする、

という3つのステップを踏まなければならないようだ。これをファイル処理に当てはめると、

・open, opendir などの引数はバイト文字にエンコードしたものを渡す。
・readdirなどで取得した結果はデコードしてからPerlで処理する。

ということになる。そして日本語Windowsの場合はシフトJISで、英語Windowsの場合はUTF-8でエンコード/デコードする。

この理解が正しいかどうか、実際にコードを書いて調べてみるつもりだ。

(参考ページ)
Shift_JIS(Wikipedia)
http://ja.wikipedia.org/wiki/Shift_JIS

File::Find 再帰的にすべてのファイルを処理する(木本裕紀)
http://d.hatena.ne.jp/perlcodesample/20080530/1212291182

perldoc perlunicode
http://perldoc.perl.org/perlunicode.html#When-Unicode-Does-Not-Happen

perldoc perlunitut
http://perldoc.perl.org/perlunitut.html

Encode 日本語などのマルチバイト文字列を適切に処理する(木本裕紀)
http://d.hatena.ne.jp/perlcodesample/20091118/1246679588
perlunitutの内容をサンプルコード付きで解説している。

(2010/01/06追記)
コメントをくれたash氏が、Win32::Unicodeをブログで紹介している。Windows環境ではこれを使うのがよいようだ。作者xaicron氏による解説記事はこちら。

Windows環境でUnicodeファイルを扱う
http://perl-users.jp/articles/advent-calendar/2009/hacker/20.html

が、ActivePerlにCPANモジュールをインストールするところがまた一苦労である。先人の解説記事をとりあえず貼り付けておく。

Perlメモ/モジュールのインストール(CPAN)

|

« Google Public DNSを使うと遅くなるかもしれない | トップページ | Perlのopendirは第2引数が0x5Cで終わるとエラーになる? »

コメント

はじめまして。

Windows環境で日本語ファイル名をperlから扱おうとすると色々とトラップが多いですね。
自分の経験は以下のサイトに書いてみましたので、参考までに。

http://ash.roova.jp/cipher/2009/12/w.html

ファイル名の文字コードを変換している時点で、本当に全てのパターンで大丈夫なのか不安ですが。

投稿: ash | 2009年12月17日 22時47分

Yet Another JPerl
http://digit.que.ne.jp/work/wiki.cgi?Perl%E3%83%A1%E3%83%A2/%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%81%AE%E6%89%B1%E3%81%84#pyajperl

Windows で日本語のパス名を扱うのに便利です。
opendir の問題も解決できますし。

投稿: | 2010年1月10日 17時34分

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



« Google Public DNSを使うと遅くなるかもしれない | トップページ | Perlのopendirは第2引数が0x5Cで終わるとエラーになる? »