はじめに
WindowsでSJISデータをperlで処理するときに、ハマりました。
コマンドプロンプトにSJISデータを標準出力表示させたら、文字化けするのです。
同じワナにハマる人がいるかもしれないので、備忘録として書いてみます。
ポイントだけ
・binmodeのレイヤはスタックされるので注意。
・複数ファイルに分かれるperlスクリプトではbinmodeでencoding変えるのは注意。
・スタック回避にはrawが使えるが、出力データ毎にencodeした方が安全。
何が問題だったか
「コマンドプロンプトに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でのencoding指定が必要な場合にはどうしたら良いのでしょうか?
この場合、: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.
「STDOUTなど共通出入口にbinmode ncoding」はまとめて設定できるので楽ですが、複数ファイルに分かれるperlスクリプトでは注意が必要です。面倒だけど外に出すデータ毎にencodeした方が失敗少なそうです。