はじめに
WindowsでSJISデータをperlで処理するときに、ハマりました。
コマンドプロンプトにSJISデータを標準出力表示させたら、文字化けするのです。
同じワナにハマる人がいるかもしれないので、備忘録として書いてみます。
私はperlエキスパートではないので、間違いとかもっと良い手段あるとか、あったら教えてください。
何が問題だったか
「コマンドプロンプトにSJISデータを標準出力表示」させるために以下を入れました。
binmode STDOUT, ':encoding(cp932)';
スクリプトはUTF-8で書いています。
ここで、「binmode」を複数個所で入れていました。
ところが、binmodeのレイヤはスタックされるので、2重変換になって文字化けします。
ダメだったスクリプトを単純化したものが、以下です。これ、文字化けします。
use strict;
use utf8;
binmode STDOUT, ':encoding(cp932)';
binmode STDOUT, ':encoding(cp932)';
my $word = "ソート機能\n";
print $word;
こんな風に1つのファイルに2個書いていたらすぐ気づくのですが、スクリプトを分割してモジュール化し、そのモジュールにもbinmodeを入れていました。
スクリプトもモジュールもステップ大きいと、すぐに気づきません。
use strict;
use utf8;
use MyModule;
binmode STDOUT, ':encoding(cp932)';
MyModule::MyFunc(); # この中にbinmodeあり
my $word = "ソート機能\n";
print $word;
じゃぁ、どうしたらいいの?
私はモジュール側のbinmodeを外しました。
モジュールの中でSTDOUTに出力していたのはデバッグ用途のためで、必須ではなかったからです。
でも、本当にモジュール側でもbinmodeが必要な場合にはどうしたら良いのでしょうか?
この場合、:rawレイヤを付けると既存の変換を無効化してくれそうです。
例えば、本体スクリプトもモジュール側も以下で書くと正しく動作します。
binmode STDOUT, ':raw:encoding(cp932)';
ただ、このレイヤの話、いろいろ機能が混じっていて難しいです。
一応、Perl自身の説明読むと
The implementation of :raw is as a pseudo-layer which when "pushed"
pops itself and then any layers which would modify the binary data stream.
と書いてあるので、過去に積んだ変換のレイヤーはチャラにしてバイナリにするようですが、この「過去に積んだ変換のレイヤーはチャラ」のためだけに:rawを使ってよいものか、まだ見切れていないのです。
さいごに
WindowsのSJISデータ処理にperlを使うのが、そもそも間違いだったかもしれません。過去の歴史もあるのでしょうが、perlでの文字コード操作は泣きたくなるくらい、難解です。