perlでsjisの文字置換。半角、全角の1バイト目2バイト目に的確に正規表現をヒットさせる術

これはperlというプログラムを使用している人向けの話になります。
その中でも、sjisの文字を扱う必要のある人にはとっておきの話題?になると思います。

   ☆★要約☆★
 sjisの環境で、羽(SJIS文字コードで FB,92) > 羽(89,48)の置換をすると、"油谷"(96,FB 92,4A)の内側2バイトがヒットし、文字化けしたようになる。
 これを回避し、正しく置換する方法です。(半角、全角が混じっていても、正しく置換・変換される。)


うちで使用しているソフトウェアに、バッチ処理で使用しているプログラムがあります。
perlというソフトです。
この中でも、DOS環境で動作している古いプログラムです。
しかも、sjisという環境で使用しているので、次のような問題がありました。

特殊な文字(第3次水準のような文字 ??)を普通の 高 に置換(変換)する。
perlでは次のように表記します。

  $_ =~ s/羽/羽/g;

その文字の組み合わせの中で、とんでもないものがありました。
  羽(FB,92)>羽(89,48)
です。
油谷という文字列を置換しようとすると、
$_ = "油谷";
 $_ =~ s/羽/羽/g;
$_の中身は、文字化けしたようになります。
なんでだろぉ~と、バイナリエディタで見ると・・・
なんと、油の2バイト目と、谷の1バイト目が、ちょうど羽の文字と同じ文字コードだったのです!
一応、jperl.exeなのですが、置換のときは1文字がどこか、というものの認識はしないようです。

そこで対応策を考えたのですが、半角文字も混ざっているので次のように作ろうと始めは思いました。
  1行を配列1つづつに分けて代入
  配列1つづつに対して置換を行う
  配列を1行にもどす
しかし! 凄く面倒くさいです。
バグが入りやすい程の複雑さになりそうです。

ネットで探してもなかなか解が見つからず、3週間ほど途方に暮れていました。
久しぶりに気を取り直して探したところ、「んっ!」というものを発見しました。

http://akebi.jp/support/cyclamen.cgi?log=perl&tree=r20
です。
これはsubstr関数(指定した場所の文字列を取得する関数)を、半角・全角文字が混ざっていても正しく取得しようとするものでした。
最初は「みんな同じようなことで悩んでいるんだなぁ」と感心して読んでいましたが、そこにピクッ!!とくるものがあるのです。

このプログラムは、半角と全角を正確に区別する為に文字の間に0x00を挿入し、処理後に元に戻しているのです。

ここでピン!!と来ました。おぉ~と一人で頷くところです(笑)
"油谷"が"油 谷"(96,FB,00,92,4A)になるので、油の2バイト目+0x00 (FB,00)と 羽(FB,92) の比較になるので、違う文字だとして変換されないのです。
ここに、perlsjisの文字を確実に(誤変換のなく)変換する術が見つかりました。 パチパチ

実際のプログラム サンプルです。

$_ = "羽油谷羽羽";
$_ =~ s/(.)/$1\0/g;
$_ =~ s/([\x81-\x9f\xe0-\xfc])\0(.)\0/$1$2\0\0/g; #半角又は、全角文字の2バイト目の次に\x0を挿入する
$_ =~ s/羽/羽/g; #置換する
$_ =~ s/??/徳/g; #このように置換したい第3次水準(dosで表示できない)文字と置換後の文字を記述していく。
$_ =~ tr/\0//d; #\x0を削除して、元に戻す

そうすると、
$_ = "羽油谷羽羽";
と、正しく変換されているはずです。

また、改行はperldosの場合は、\rを使用します。(\nはいらないです。念のため自身の環境で確認してください。)