tests/auto/qtextstream/shift-jis.txt
changeset 0 1918ee327afb
child 1 ae9c8dab0e3e
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 Shift-JISテキストを正しく扱う
       
     2 最近の更新履歴
       
     3 2005-03-26: 「最初に」中、XML日本語プロファイル第2版に基づき、若干追記。 
       
     4 2005-03-09: 「最初に」中、文章を若干修正。 
       
     5 2003-06-24: Shift-JISの漢字を含むファイル名/パス名 
       
     6 2003-05-31: 「最初に」中、「シフトJIS」などの表記について。 
       
     7 2003-05-24: CP932重複定義文字の変換 
       
     8 2002-08-30: Perl 5.8.0 について。 
       
     9 2002-01-17: 長い文字列に対する正規表現検索 
       
    10 2001-12-15: ShiftJIS::Collate が overrideCJK パラメータを廃止したことに伴う 日本語文字列を並び替えるの書き換え。 
       
    11 最初に 
       
    12 ありがちなエラー 
       
    13 エラーや間違いを防ぐ対策 
       
    14 文字列リテラルの作り方 
       
    15 正規表現とマッチ 
       
    16 グローバルマッチ 
       
    17 アルファベットの大文字と小文字 
       
    18 長い文字列に対する正規表現検索 
       
    19 外字の変換 
       
    20 CP932重複定義文字の変換 
       
    21 文字数を数える 
       
    22 文字単位に分割する 
       
    23 いろいろな分割 
       
    24 特定の長さで切りそろえる 
       
    25 日本語文字列を並び替える 
       
    26 Shift-JISの漢字を含むファイル名/パス名 
       
    27 最初に
       
    28 日本語の文字コードにはいくつかのものが使われています。ある程度一般的なものなら、どれを使ってもよいでしょう(どの文字コードで符号化されているかの情報が失われさえしなければ)。
       
    29 例えば、日本語版Windowsでは、メモ帳でもDOS窓でもShift-JISが使われています。こういう場合、処理の途中でわざわざEUC-JPやUTF-8に変換するとしたら面倒です。デバッグのとき、「この段階ではこの変数には何が入っているのか」出力して点検するのはよく行われますが、このときEUC-JPとして収められていたら、作業は手間どるでしょう。入力も出力もShift-JISで行うつもりなら、処理の全体でShift-JISのまま扱えたらきっと便利でしょう。
       
    30 注: "シフトJIS", "Shift_JIS", "Shift-JIS" などの表記の違いについてはよくわかっていません。今のところ分かっていることは:
       
    31 JIS X 0208:1997の附属書1(シフト符号化表現)には、「参考」として「この符号化表現は通常“シフトJISコード”と呼ばれている」の記述があります。 
       
    32 IANA の CHARACTER SETS には、Shift_JIS と Windows-31J とが別に登録されています。また、Shift_JISについて、「CCS(符号化文字集合)はJIS X0201:1997とJIS X0208:1997であり、完全な定義はJIS X 0208:1997の附属書1に示されている。」と記しています。 
       
    33 W3C の XML Japanese Profile には、Shift-JISにUnicodeへの変換表が複数ある旨の記載があります。XML Japanese Profile (Second Edition)では、Unicode Consortiumで公開されているMicrosoft CP932の変換表によるcharsetの名称 "x-sjis-cp932" を "Windows-31J" に変更しています。 
       
    34 Microsoft社の Global Dev では、Codepage 932 を "Japanese Shift-JIS" と注記しています。 
       
    35 しかし、Shift-JISにはある種の癖があって、ちょっとしたことがバグやエラーや文字化けの原因となります。なんとかならないものでしょうか。
       
    36 Perlは制御文字やナル文字を含むバイナリデータですら正しく処理できるように設計されているので、スクリプトやテキストをShift-JISで書いたくらいで問題になることはありません。
       
    37 しかし、perlがスクリプトを解釈するときは(通常)バイト単位で調べるので、Shift-JISのようなマルチバイト文字を含む符号はそのままでは直接理解できません。
       
    38 たとえば、Shift-JISで 'あ' という文字は、16進数で82 A0という2バイトで表されます。これを "\x82\xA0" と書いてもperlにとっては同じです。これが日本の(country)日本語の(language)文字であるとか、Shift-JISで書かれている(charset)とかいう情報はどこにも含まれていません。
       
    39 そのため、Shift-JISで書きたいときには、perlの誤解を受けないように書いてやらなければなりません。その配慮は、プログラマがしてやらなければなりません。この文書の記述は、そのような手間をかけても、Shift-JISを用いることに意義があると考えている人には参考になるかもしれません。
       
    40 そんな手間を掛けたくない人は、 
       
    41 Perl 5.8.x以降を使う。 
       
    42 利点:perl5-porters@perl.org でサポートされている。 
       
    43 欠点:独特の考え方があり、従来の日本語処理とは相容れないところがある(もっとも、そのうち慣れて気にならなくなるかもしれない)。 
       
    44 jperlを使う。 
       
    45 利点:Shift-JIS を文字として直接扱うことができる。 
       
    46 欠点:現在、維持する人がいない。 
       
    47 文字コードをUTF-8かEUC-JPに変換してから処理する。 
       
    48 利点:Perl 5.8.x以降でなくても動作する変換用のモジュール(.pm)やライブラリ(.pl)がいろいろ入手可能。 
       
    49 欠点:Shift-JISほど悪くないにしても、マルチバイト文字をシングルバイト文字と区別せず、ともに一文字として処理するのは面倒である。 
       
    50  という対処をとったほうがよいでしょう。これらのプログラムは有名なので、探せばすぐ見つかるでしょうから、入手先はここには示しません。
       
    51 なお、この文書に書かれている事が、最も勧められない方法なので、ここから先は、そのつもりでお読み下さい。この方法について何か疑問が生じたとしても、それについて他のところで質問すると、何でそんなやり方をしているのかと、きっと非難されるでしょう。かといって、私にも訊かないで下さい。
       
    52 Shift-JISを使ったときにありがちな(?)エラー
       
    53 Shift-JISには、第2バイトが [@-~](ASCII 10進数で64-126)の範囲に入るものがあります。これらのASCII文字は、perlにとって特別な意味をもつことがあるため、しばしばエラーの原因となります。Shift-JISでは、2バイト文字の第2バイトは、[\x40-\x7E\x80-\xFC])の範囲にあるため、実に188分の63、約3分の1の文字が何らかの問題を起こし得るといえます。
       
    54 次に、Shift-JISを使ったときに起こりがちなエラーとその原因を示します。エラーメッセージはperlの違い(バージョンやどのプラットフォーム用のものであるか等)により多少の違いがあるかもしれません。
       
    55 エラーにならなくても、文字化けしたり、期待したような動作をしなかったりで、うまくいかないことがあります。この場合、エラーが出ない分、原因を自分で探さなければならなくなるためバグ取りはしばしば困難です。
       
    56 ここではエラーに対する対策は提示しません。対策はあとでまとめて書きます。
       
    57 なお、ここには文字コードをEUC-JPにしても起こるような問題やエラーは示しません。基本的に、EUC-JPなら起きないが、Shift-JISのときには起こるような事柄に限ります。
       
    58 エラーにはならないけど文字化けする(1) 
       
    59 例えば、"表示" とか "暴力" とかいうリテラルが文字化けを起こします。これらは "侮ヲ" とか "沫ヘ" になります。これは、"表" や "暴"の文字の第2バイトが \ であるため、ダブルクオート文字の中では次の文字のエスケープをすることになるので、表示 = 0x955C8EA6 であっても、クオートの結果は "表示" = 0x958EA6 となるからです。'表示' とすれば文字化けは起こりませんが、シングルクオートでも防げない文字化けやエラーがあります(次例)。 
       
    60 エラーにはならないけど文字化けする(2) 
       
    61 例えば、"ミソ\500" というリテラルでは、\ が脱落してしまいます。これは、'ミソ\500' や q(ミソ\500) などとしても防ぐことができません。それは \\ という連続があると \ 1個になってしまうという規則があるからです。 
       
    62 クオートやクオート風演算子の中では、文字列にクオートと同じ文字を含められるように、\ によるエスケープを付ければクオートの終端文字ではなく、文字列の一部とみなします。そのため、\\ が \ の文字を表すエスケープになります。これはクオートの始端・終端文字を何にしても同じことです。
       
    63 エラーにはならないけど文字化けする(3) 
       
    64 例えば、"丸十net" というリテラルが文字化けを起こします。これは "丸・
       
    65 et" のように途中で改行されてしまいます。これは、"十" の第2バイトが \ であるため、ダブルクオート文字の中では次の 'n' と合わせて\nのすなわち改行文字を表すメタ文字として解釈されるからです。 
       
    66 エラーにはならないけど文字化けする(4) 
       
    67 例えば、"引数 ARGV" というリテラルが文字化けを起こします。これは、" "(全角スペース)の第2バイトが @ であるため、ダブルクオート文字の中では次の ARGV と合わせて "@ARGV" という配列として変数展開を行うからです。@ARGVのように必ず定義されるような配列なら、展開されますが、別の場合ではエラーになるかもしれません(それは次項を参照)。 
       
    68 In string, @dog now must be written as \@dog (Perl 5.6.0まで) 
       
    69 「文字列の中では、@dogは今は\@dogと書かなければならない」 
       
    70 前例でみたように、全角スペース " "の第2バイトは @ であるため、後ろの文字と合わせて配列であるかのように解釈しようとします。"犬 dog" のような場合、@dog という配列が定義されていればそれを用いて変数展開しますが、定義されていない場合、エラーメッセージを出します。 
       
    71 ``now must be written as''「今はこう書かなければならない」とは、Perl4までは配列の変数展開は行わなかったため、"hoge@foo.bar" のような書き方をすることができたのだが、今 Perlでは @foo が展開されてしまうので、注意を喚起するためエラーを出すようにしているようです(もしPerlが昔から配列の展開をサポートしていたら、エラーを出すことなく、黙って展開するだけだったかもしれません。次項も参照)。
       
    72 "犬 \dog" とすればいいという意見もありますが、\d がメタ文字として特別意味がないためにうまくいくのであって(Perl 5.6以降では、警告 Unrecognized escape \d passed through 「認識できないエスケープ \d が渡された」を引き起こします)、例えば "花 \flower" のときは、\f が改ページ文字として解釈され、文字化けします。
       
    73 Possible unintended interpolation of @dog in string (Perl 5.6.1以降) 
       
    74 文字列の中で、@dogが予期せずに展開される 
       
    75 前項と同じく、"犬 dog" ですが、Perl 5.6.1(ActivePerl 626)以降では、定義されていない配列でも黙って展開します。配列 @dog が展開されるので、"犬\x81" と同じになります。 
       
    76 これはエラーではなく、警告になります。
       
    77 Can't find string terminator '"' anywhere before EOF 
       
    78 「終端文字 '"'がファイルの終り EOF までに見つからなかった」 
       
    79 例えば、"対応表" のようなリテラルでは、'表' の第2バイトが \ であるため、うしろの " をエスケープしてしまいます。このためperlは、その " はクオート文字列の終端文字とはみなさずに、文字列がさらに続くと考えてしまいます。これ以降、スクリプトの中に " の文字が全く含まれなければ、このようにエラー報告をします。 
       
    80 qq{ "日本語" }のような場合にも注意しなければなりません。"本" の第二バイトは { なので、このままでは { }のネストがずれてしまい、同様のエラーが発生します。
       
    81 Bareword found where operator expected 
       
    82 「裸の語が演算子があってほしい位置に見つかった」 
       
    83 例えば、print "<img alt=\"対応表\" height=115 width=150>"; のような場合、\" による引用符のエスケープは、表 の第2バイトの\のため、\\ " という組み合わせになり、エスケープが打ち消されています。そのため、このリテラルは、perlから見ると、"<img alt=\"対応表\" で終わっています。そのため、リテラルの後に、height という「裸の語」(クオートで囲まれていない文字列)があるようにみて、ここには裸の語ではなく、演算子があるべきではないか?とperlは考えます。 
       
    84 Unrecognized character \x82 
       
    85 「認識されない文字 \x82」 
       
    86 これは、非ASCII文字やその他の文字を「裸の語」にしたときに出るメッセージです。"対応表" のようなリテラルがあって、そのあとに "なんでもいいけど" のようなリテラルがあったとき、前例と同じ理由から起こるものです。 
       
    87 また、q{マッチ} のような場合にも、'マ' の第二バイトが } なので、{ } のカッコはそこで終わってしまい、同様なエラーになります。
       
    88 マッチしないはずなのにマッチする(1) 
       
    89 "ヤカン" =~ /ポット/ はマッチします。それは、'ポ' の第二バイトが | なので、/ポット/ は /\x83|ット/ とみなされ、\x83 だけマッチすればよいからです。 
       
    90 マッチしないはずなのにマッチする(2) 
       
    91 "兄弟" =~ /Z/ はマッチします。それは、'兄' の第二バイトが 'Z' だからです。第二バイトがアルファベットになる文字には注意が必要です。 
       
    92 マッチするはずなのにマッチしない(1) 
       
    93 "運転免許" =~ /運転/ はマッチしません。それは、'運' の第二バイトが '^'なので、/運転/ は /\x89^転/ とみなされ、文字列の始め ^ の前に \x89 はないからです。 
       
    94 Search pattern not terminated 
       
    95 「サーチパターンが終了しない」 
       
    96 これは、/表/ のように、第二バイトが \ である文字でサーチパターンを終わらせようとしたときに起こります。マッチ演算子の終端文字 / をエスケープしてしまうので、サーチパターンがさらに先に続くように解釈されます。その先にもう一度/はありますか? 
       
    97 あったところで、別のエラーが発生するでしょう。
       
    98 Substitution replacement not terminated 
       
    99 「置換操作の置換文字列が終了しない」 
       
   100 置換演算子は s/PATTERN/REPLACEMENT/の形式をとらねばなりません。しかし s/表/裏/; のように、第二バイトが \ である文字でPATTERN部分を終わらせようとしたときにこのエラーが起こります。マッチ演算子の終端文字 / をエスケープしてしまうので、PATTERNがさらに先に続くように解釈されます。そのためperlは、PATTERNは 表/裏 の部分であると考え、3番目の/の先にREPLACEMENT部分があるに違いないと思うのですが、その先にもう一度/はありますか? 
       
   101 あったところで、別のエラーが発生するでしょう。
       
   102 unmatched [ ] in regexp 
       
   103 「正規表現にマッチしない [ ] がある」 
       
   104 例えば、/プール/ ではエラーが起こります。それは 'ー' の第二バイトが [ なので、/プール/ は /プ\x81[\x83\x8B/ とみなされ、perlは文字クラスがあるのではないかと思います。しかし文字クラスの終了を示す ] が見つからないのでエラーになります。 
       
   105 エラーにはならないけど文字化けする(5) 
       
   106 例えば、lc('アイウエオ')は、'ヂツテトナ'を返します。Shift-JISの2バイト文字の中には、第2バイトがASCIIで英字に当たるものがあります。詳しくはアルファベットの大文字と小文字をご覧下さい。 
       
   107 エラーや間違いを防ぐ対策
       
   108 以上のようなエラーを防ぐにはさまざまな方法が考えられます。例えば、"表\示" と書けばいいなどという提案があります。もちろんそれでもかまいません。しかしそのためにはどの文字の後に \ を入れればいいかを知る必要があります。それは文字コード表を見れば一発で明らかです。
       
   109 …などという面倒なことが苦にならない人、文字コード表なんか(少なくとも問題になるような文字くらいは)暗記してしまえばいいというような人にとっては、確かにそれで解決になると思います。しかし、そのような人には、こんなページを見にくる必要もヒマもないでしょう。
       
   110 そこで、このようなページをわざわざ見にくるような人は、文字コード表をいちいち調べたくないひとだと仮定します。別にそうだからといって非難されることはありません。しかし、手間を惜しむあまり間違ったプログラムを平気で作っていては、顔にクリームパイをぶつけられても仕方ありません。
       
   111 文字列リテラルの作り方
       
   112 よく知っておかねばならないことは、\ というエスケープ用の文字は、変数展開やメタ文字の解釈よりずっと前の段階でさまざまな影響を及ぼすということです。そのため、どうしたらデータを確実に変数の中に収められるかを考える必要があります。変数の内部に収めてしまえば、Perlがデータを適切に管理してくれます。よく知られている $str = "表示" の文字化けも、変数$strに代入する以前、ダブルクォートで囲んだ時点ですでに文字化けしていると考えるべきです。すでに文字化けしたデータを代入して、好い結果が得られるはずがありません。
       
   113 ヒアドキュメントは安全性の高い解決法です。ただし、終端文字列をシングルクォートで囲んでやらなくてはなりません。ダブルクォートで囲んだり、クォートを付けなかったりでは、予期せぬ変数展開やメタ文字の解釈を防ぐことができません。
       
   114 シングルクォートで終端文字列を囲んだヒアドキュメントでは、変数展開やメタ文字の解釈は何も起こりません。ただ、終端文字列(この場合は "\nEOF\n")を探すことだけを行います。ヒアドキュメントを使うと文字列に改行文字がつきますが、chompで除くといいでしょう。
       
   115 次の例は $str = 'ここにテキストを書く' と同じように働きますが、文字列の内容によって問題が起こりません。書いたとおりにリテラルを代入できると期待できます。
       
   116 サンプルコード 
       
   117 chomp($str = << 'EOF');
       
   118 ここにテキストを書く
       
   119 EOF
       
   120 
       
   121 $src = << 'EOF';
       
   122   $path = "C:\\path\\file";
       
   123   open FH, $path;
       
   124 EOF
       
   125 
       
   126 多くの文字列を一度に作りたければ、splitで分割すると容易に作れます。
       
   127 サンプルコード 
       
   128 ($name, $career, $age, $memo) = split "\n", << 'EOF';
       
   129 田中一郎
       
   130 プログラマ
       
   131 三十五
       
   132 大福が好物である。  酒はあまり呑まない。
       
   133 EOF
       
   134 
       
   135 もう少し簡潔に書きたければ、空白文字、\(2バイト文字に含まれているのは構わない)、およびカッコを含まないという条件で、qw() を使うことができます。例えば、@str = qw(表示 対応表 );のように空白を入れてカッコのエスケープを防ぎます。@str = qw(表示 対応表);のように空白を入れないとエラーの元です。
       
   136 1つの文字列を作る時でも、左辺を丸カッコで囲んでリストコンテキストを示すか、右辺をスライスにするかしなければなりません。これは、(現状では)qw// は split(' ', q//) の略記として実装しているからです。なお、Perl 5.6ではリストと等価になっているようです。
       
   137 サンプルコード 
       
   138 ($str) =  qw(百三十 );
       
   139  $str  = (qw/百三十 /)[0];
       
   140  $str  =  qw/百三十 /; # Perl 5.6
       
   141 
       
   142 正規表現とマッチ
       
   143 正規表現のメタ文字は多いので、正規表現の中にShift-JISの文字列を埋めこむのは得策ではありません。例えば、/\Q対応表/ ではエラーになります。これは、/ / の範囲の決定が真っ先に行われ、その時点でエラーが発生するので、\Q の効果を及ぼすことができないからです。また、/\Q対応表\E/ は巧く行きません。これは、対応表\E という文字列を含むものにしかマッチしません。これは、\\ という連続があるため、\E が認識されないからでしょう(たぶん)。
       
   144 そのため、変数に入れて、マッチ演算子や置換演算子の中で展開させるとよろしいです。このとき日本語文字列は予めquotemeta 関数で処理しておきます。
       
   145 サンプルコード 
       
   146 $pat = quotemeta +(qw/ 表 /)[0];
       
   147 $str =~ /$pat\d+/; # 表1, 表2, ..などにマッチ
       
   148   # しかし $str = '剣\\1' でもマッチする(この問題は後述)
       
   149 
       
   150 上のような書き方は確かに醜いですね。クオートの中で \Q \E を使う時は、正しい文字列が入っている変数と一緒になら問題が起こりません。こうすることで、\Q \E の範囲が明確になるからです。正しい文字列の作り方は、前述のリテラルの作りかたを参考にして下さい。
       
   151 サンプルコード 
       
   152 $pat = "(?:\Q$str1\E|\Q$str2\E)*";
       
   153 $str =~ /$pat/;
       
   154 
       
   155 # 実は上の文は次の文と等価。
       
   156 # $pat = "(?:" . quotemeta($str1) . "|" . quotemeta($str2) . ")*";
       
   157 
       
   158 リテラルをクォートの中に直接埋めこむとうまく行かないことがあります。それは、perlが \E というメタ文字を発見しようというのを、Shift-JIS文字が妨げるためです。
       
   159 "\Q表\E"では、表Eにマッチする正規表現になります。表の第二バイトの \ と次の \が合わさるので、perlには\Q \x95 \\ Eの組み合わせであるように思われます。 \Qの作用の結果は\\x95\\x5cEになります。そのため、表Eにマッチします。
       
   160 "\Q表"はどうでしょう。この場合は、" " の範囲を決めるときに、表の第二バイトが後のクォートをエスケープしてしまうので、文字列の範囲が期待したようには定まらず、エラーになります。このエラーは\Qの効果を考慮する前に発生するので、防ぎようがありません。
       
   161 "\Q表\\E"はどうでしょう。確かにShift-JISでは問題ありません。しかし同じスクリプトをEUC-JPやUTF-8に変換したときには問題があります。表\Eと余分な2文字がある文字列でないとマッチしません。どちらにしろ、\をどこに添えるかを考える必要があるので、ここの趣旨に合いません。
       
   162 正規表現は例えば、次のようにします。もちろんこれはShift-JISのみに有効です。
       
   163     $digit = '(?:[0-9]|\x82[\x4F-\x58])'; # 数字(半角と全角)
       
   164     $upper = '(?:[A-Z]|\x82[\x60-\x79])'; # アルファベット大文字(半角と全角)
       
   165     $lower = '(?:[a-z]|\x82[\x81-\x9A])'; # アルファベット小文字(半角と全角)
       
   166     $space = '(?:[\ \n\r\t\f]|\x81\x40)'; # 空白文字(半角と全角)
       
   167     $ascii = '[\x00-\x7F]';               # ASCII文字
       
   168 
       
   169     # 全角平仮名(濁点・半濁点・踊り字を含む)
       
   170     $hiraZ = '(?:\x82[\x9F-\xF1]|\x81[\x4A\x4B\x54\x55])'; 
       
   171 
       
   172     # 全角片仮名(長音符・濁点・半濁点・踊り字を含む)
       
   173     $kataZ = '(?:\x83[\x40-\x7E\x80-\x96]|\x81[\x5B\x4A\x4B\x52\x53])';
       
   174 
       
   175     # 半角片仮名(半角長音符・句読点を含む)
       
   176     $kataH = '[\xA1-\xDF]';
       
   177 
       
   178     $onebyte = '[\x00-\x7F\xA1-\xDF]';
       
   179     $twobyte = '(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; 
       
   180     $char    = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   181 
       
   182     # JIS文字
       
   183     $all_JIS = '(?:[\x00-\x7f\xa1-\xdf]|'.
       
   184         . '\x81[\x40-\x7e\x80-\xac\xb8-\xbf\xc8-\xce\xda-\xe8\xf0-\xf7\xfc]|'
       
   185         . '\x82[\x4f-\x58\x60-\x79\x81-\x9a\x9f-\xf1]|'
       
   186         . '\x83[\x40-\x7e\x80-\x96\x9f-\xb6\xbf-\xd6]|'
       
   187         . '\x84[\x40-\x60\x70-\x7e\x80-\x91\x9f-\xbe]|'
       
   188         . '\x88[\x9f-\xfc]|\x98[\x40-\x72\x9f-\xfc]|\xea[\x40-\x7e\x80-\xa4]|'
       
   189         . '[\x89-\x97\x99-\x9f\xe0-\xe9][\x40-\x7e\x80-\xfc])';
       
   190 
       
   191     # ベンダ定義文字
       
   192 
       
   193     # NEC特殊文字
       
   194     $NEC_special = '(?:\x87[\x40-\x5d\x5f-\x75\x7e\x80-\x9c])';
       
   195 
       
   196     # NEC選定IBM拡張文字
       
   197     $NEC_IBM_ext = '(?:\xed[\x40-\x7e\x80-\xfc]|\xee[\x40-\x7e\x80-\xec\xef-\xfc])';
       
   198 
       
   199     # IBM拡張文字
       
   200     $IBM_ext     = '(?:[\xfa-\xfb][\x40-\x7e\x80-\xfc]|\xfc[\x40-\x4b])';
       
   201 
       
   202 
       
   203 Shift-JISでマッチを行う時には、2つの問題があります。
       
   204 第二バイトがASCIIの領域に入る文字があるので、ASCIIを含むパターンにマッチする可能性がある。 
       
   205 ある文字の第二バイトと次の文字の第一バイトが1文字であるかのようにマッチしてしまう。 
       
   206 後者はEUC-JPでも起こりうる問題です(UTF-8なら起こらないが、今はそれが問題なのではない)。しかし前者はEUC-JPでは起こらないが、Shift-JISでは起こりうる問題です。これらを防ぐ方法は、結局同じことですが、正規表現の中に、常に先頭を含ませることです。
       
   207 サンプルコード 
       
   208 # 先頭からマッチ
       
   209 $str =~ /^$char*?(?:$pat)/;
       
   210 
       
   211 末尾からのマッチではうまく行かないことがあります。"右" =~ /E$/を考えれば十分でしょう。また、"\x8E" x 30 . "E"は$str = "試試試試試試試試試試試試試試試E"であるが、"\x8E" x 31 . "E"は$str = "試試試試試試試試試試試試試試試殺"でありますから、Shift-JIS文字列を後ろから切り分ける適切な方法はないと考えられます。
       
   212 少なくとも、2バイト文字を構成しないバイト [\x00-\x3F\x7F] が見つかる所まで、極端な場合は文字列の最初までスキャンしないとわからず、しかも後読み lookbehind の正規表現 (?<=PATTERN)は今の所、不定長にできません((?<=(?:\A|[\x00-\x3F\x7F])$char*) とはできない)ので、先頭から文字単位でばらしてから処理するのが、結局は簡便なのかもしれません。
       
   213 グローバルマッチ
       
   214 グローバルマッチ /g の場合は、\Gを使いましょう。\Gは前回マッチした部分の末尾を指します。
       
   215 次の例では、置換されないのが望ましいのですが、\Gがないので、先頭から開始して文字列全体まで延びてマッチしなかったあと、改めて先頭から1バイト進んだ位置からスキャンを再開するので、ずれた位置なのにマッチしたと考えてしまいます。\Gを使わないと、間違った位置にマッチするかもしれないうえに、余計な再検査をするので、時間もかかります。
       
   216 サンプルコード 
       
   217     $str = '試試試試E試試試試E';
       
   218     $pat = '殺';
       
   219     $str =~ s/\G($char*?)($pat)/${1}E/og;
       
   220     # '試試試試E試試試試E' のまま(正しい)。
       
   221 
       
   222 \Gを付けない場合
       
   223 
       
   224     $str = '試試試試E試試試試E';
       
   225     $pat = '殺';
       
   226     $str =~ s/($char*?)($pat)/${1}E/og; # '殺' があれば 'E' に置換
       
   227     print  $str;
       
   228     # '試試試殺試試試殺' になってしまう(おかしい)。
       
   229 
       
   230     試試試試E試試試試E
       
   231 1回目 →→→→/→→→→/  (マッチしない)
       
   232 2回目 |→→→⇒|||||||||  (マッチしたので置換)
       
   233 3回目 |||||||||→→→→/  (マッチしない)
       
   234 4回目 ||||||||||→→→⇒  (マッチしたので置換)
       
   235 
       
   236  凡例: → $charが2バイト文字にマッチ
       
   237      / $charが1バイト文字にマッチ
       
   238      ⇒ $patがマッチ
       
   239      | スキャンの範囲外
       
   240 
       
   241 ただし、パターンがゼロ文字幅にマッチする場合には、注意が必要です。次の例は、「ア」の前に 'Z' を入れるものです。第1例は文字のずれ('ア' : 0x8341に対して'泣A' : 0x8B8341がずれてマッチする)を防いでいません。第2例は、上の方法で「ずれ」を防ごうとしたのですが、Z への置換が連続して起こっています。
       
   242 これは第3例のようにする必要があります。これは、第2例では「なぜ?」に書いたように、置換されるからと考えられます。
       
   243 サンプルコード 
       
   244 $str = "アイウエアアイウア泣A";
       
   245 
       
   246 print +($temp = $str) =~ s/(?=ア)/Z/g, $temp;
       
   247 
       
   248 print +($temp = $str) =~ s/\G($char*?)(?=ア)/${1}Z/g, $temp;
       
   249 
       
   250 print +($temp = $str) =~ s/\G(\A|$char+?)(?=ア)/${1}Z/g, $temp;
       
   251 
       
   252 結果 
       
   253 5    ZアイウエZアZアイウZア技ア
       
   254 7    ZアイウエZZアZZアイウZZア泣A
       
   255 4    ZアイウエZアZアイウZア泣A
       
   256 
       
   257 なぜ? 
       
   258      ア   イ   ウ   エ   ア   ア   イ   ウ   ア   泣   A
       
   259 1  \G Z
       
   260 2  \G$char$char$char$char Z
       
   261 3                      \G Z
       
   262 4                      \G$char Z
       
   263 5      以下、省略
       
   264 
       
   265 つまり、グローバルマッチでは、マッチがゼロ文字幅でないパターンの前には\G($char*?)を、ゼロ文字幅であるパターンの前には\G(\A|$char+?)を入れる必要があります。
       
   266 ただし、これでも正しく(?)マッチさせられない場合があります。
       
   267 サンプルコード 
       
   268 $str = "0123000123";
       
   269 
       
   270 print +($temp = $str) =~ s/0*/Z/g, $temp;
       
   271 
       
   272 print +($temp = $str) =~ s/\G($char*?)0*/${1}Z/g, $temp;
       
   273 
       
   274 print +($temp = $str) =~ s/\G(\A|$char+?)0*/${1}Z/g, $temp;
       
   275 __END__
       
   276  9    ZZ1Z2Z3ZZ1Z2Z3Z
       
   277 14    ZZ1ZZ2ZZ3ZZ1ZZ2ZZ3ZZ
       
   278  7    Z1Z2Z3Z1Z2Z3Z
       
   279 
       
   280 これは、パターンがゼロ文字幅にマッチするものであると、/gがその場で無限に足踏みして終了しなくなるのを防ぐため、perlは、マッチを強制的に進めているのですが(cf. perlre, Repeated patterns matching zero-length substring)、この進め方の真似(しかもバイト単位ではなく、文字単位で進むもの)が、\G($char*?)や\G(\A|$char+?)では、うまくできないからです。もっともこういうマッチをすることは、ほとんどないと考えられますので、気にする必要はないのかも知れません(<負け惜しみ)。
       
   281 アルファベットの大文字と小文字
       
   282 Shift-JISの2バイト文字の中には、第2バイトがASCIIで英字に当たるものがあります。そのため、関数 uc, lc や、メタ文字 \U, \Lが2バイト文字の一部を変換してしまったり(しかし関数 ucfirst, lcfirst や メタ文字 \u, \l は問題とならない)、m//i や s///iなどの /i修飾子によって違う文字なのにマッチしてしまったりすることがあります。
       
   283 Shift-JIS文字列に含まれるASCIIの英字を大文字または小文字に揃えたいなら、例えば、次のようなサブルーチンを作れば実現できます。
       
   284 サンプルコード 
       
   285 $char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   286 
       
   287 lc("PERLプログラミング");      # 'perlプロバラミンバ'
       
   288 tolower("PERLプログラミング"); # 'perlプログラミング'
       
   289 
       
   290 sub tolower {
       
   291   my $str = $_[0];
       
   292   $str =~ s/\G($char*?)([A-Z]+)/$1\L$2/g;
       
   293   $str;
       
   294 }
       
   295 
       
   296 sub toupper {
       
   297   my $str = $_[0];
       
   298   $str =~ s/\G($char*?)([a-z]+)/$1\U$2/g;
       
   299   $str;
       
   300 }
       
   301 
       
   302 ケース無視のマッチ /i の場合は、例えば 'エ'の第二バイトは 'G' であり、'ト'の第二バイトは 'g' であることから、'エ' =~ /ト/iはマッチします。ですから、Shift-JISで正確なマッチをしたければ、/i修飾子は使うことができません。
       
   303 かわりに、文字列に含まれるアルファベット(二バイト文字の第2バイトにあるものを除く)を小文字(または大文字、どちらか)に統一してマッチさせます。tolowerの定義は上をご覧下さい。
       
   304 サンプルコード 
       
   305 $char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   306 
       
   307 $lcstr = tolower($str);
       
   308 $lckey = tolower(quotemeta $key);
       
   309 
       
   310 if ($lcstr =~ /^$char*?$lckey/) {
       
   311     print "matched";
       
   312 }
       
   313 else {
       
   314     print "not matched";
       
   315 }
       
   316 
       
   317 または埋め込み修飾子 (?ismx-ismx) を用いても好い結果を得られます。
       
   318 サンプルコード 
       
   319 "第1回Perl講縮のご案内" =~ /^$char*?PERL講習/i       # マッチする(困る)
       
   320 "第2回Perl講縮のご案内" =~ /^$char*?((?i)PERL)講習/  # マッチしない(良し)
       
   321 "第3回Perl講習のご案内" =~ /^$char*?((?i)PERL)講習/  # マッチする(良し)
       
   322 
       
   323 全角2バイトアルファベットのケース無視は、選択により実現できます(が、やっぱり変)。'A'の第2バイトが '`' なのも、ちょっと注意です(m`` などのとき致命的エラーになる。ただしバッククォートを使う意味は特にない)。原則的にはリテラルをマッチ演算子や置換演算子に直接埋め込むのは避けたい所です。
       
   324 サンプルコード 
       
   325 /(?:P|p)(?:E|e)(?:R|r)(?:L|l)/;
       
   326 
       
   327 その代わりにこんなサブルーチンを作ってみてもよいかもしれません。
       
   328 サンプルコード 
       
   329 $CharRE = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   330  
       
   331 $pat = make_regexp_ignorecase("PERL講習");
       
   332 print "第5回Perl講習会" =~ /^$char*?$pat/ ? "OK": "NOT";
       
   333 
       
   334 sub make_regexp_ignorecase {
       
   335   my $str = $_[0];
       
   336   $str =~ s/\G([A-Za-z]+|$CharRE)/
       
   337     my $c = ord $1;
       
   338     if($c == 0x82) {
       
   339       my $v = vec($1,1,8);
       
   340       0x81 <= $v && $v <= 0x9A ? sprintf('\\x82[\\x%2x\\x%2x]', $v, $v-33) :
       
   341       0x60 <= $v && $v <= 0x79 ? sprintf('\\x82[\\x%2x\\x%2x]', $v, $v+33) :
       
   342       quotemeta($1);
       
   343     } 
       
   344     elsif(0x41 <= $c && $c <= 0x5A || 0x61 <= $c && $c <= 0x7A) {"(?:(?i)$1)"}
       
   345     else {quotemeta($1)}
       
   346   /geo;
       
   347   $str;
       
   348 }
       
   349 
       
   350 長い文字列に対する正規表現検索
       
   351 正規表現は、Perl にとって欠かせない存在といえます。しかし正規表現の制限として、*, +, {min,max} などの量指定子がマッチを繰り返せる回数の上限という問題があります。(詳細は perlre 参照のこと)。そのため、$char*? という正規表現には、危険性があります。
       
   352 例えば、次のようなマッチングを考えて見ましょう。$strは、「あ」が10万字連続したあとに、「アイABC」が連結された文字列です。このような文字列(ただし、「『あ』が10万字連続」ということは分からず、任意の Shift-JIS テキストであろうということにします)から、半角アルファベットが連続した部分を見つけたいとしましょう。すると、今までの考え方からすると、次のようにすればよいと思われます。
       
   353 サンプルコード 
       
   354 my $char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   355 my $str = ('あ' x 100000) . 'アイABC';
       
   356 $str =~ /^$char*?([A-Z]+)/o;
       
   357 print $1;
       
   358 
       
   359 しかし、これは、環境によっては、大きなエラーを引き起こします。例えば、Windows 98上で Active Perl 522 を用いた場合、Error: Runtime exception という Perl のエラーになりました。また、Windows 98上でVC++ 6.0でコンパイルされた Perl 5.6.1 だと、「このプログラムは不正な処理を行ったので強制終了されます。〜」などといったエラーになりました。
       
   360 このような問題をできるだけ防ぐためには、次のようにします。つまり、文字列の先頭から調べていく場合、二バイト文字の文字境界を間違えるのは、二バイト文字の第一バイトの直後を文字境界と誤認識した時だけです。Shift-JISでは、二バイト文字の第一バイトは、[\x81-\x9F\xE0-\xFC] だけです。あるいは、EUC-JP に変換可能な領域だけを考慮すれば、[\x81-\x9F\xE0-\xEF] だけということができます。それ以外のバイトの直後は、例えば、0x41 の直後は、'A' の直後か、'ア' の直後かは分かりませんが、確かに文字境界になります。従って、[\x81-\x9F\xE0-\xFC]+ (または [\x81-\x9F\xE0-\xEF]+ )のバイト(二バイト文字)が連続するところだけに注意すればよいことになります。
       
   361 このため、以下のように、^$char*? の代わりに $Apad を使い、\G$char*? の代わりに $Gpad を用いれば、一バイト文字か、二バイト文字のうち第二バイトが [\x40-\x7E\x80\xA0-\xDF] で終わるものが、少なくとも適当な間隔で(上限に達しないうちに)出現すれば、エラーにならずに処理することができます。(確率的な問題ですので、完全ではありません。)
       
   362 サンプルコード 
       
   363 # 一回だけマッチ
       
   364 my $Apad  = '(?:(?:\A|[\x00-\x80\xA0-\xDF])(?:[\x81-\x9F\xE0-\xFC]{2})*?)';
       
   365 my $str1 = ('あ' x 100000) . 'アイABC';
       
   366 $str1 =~ /$Apad([A-Z]+)/o;
       
   367 print "$1\n"; # "ABC" と表示される。
       
   368 
       
   369 # グローバルマッチ
       
   370 my $Gpad  = '(?:(?:\G|[\x00-\x80\xA0-\xDF])(?:[\x81-\x9F\xE0-\xFC]{2})*?)';
       
   371 
       
   372 my $str2 = 'あ' x 100000 . 'アイABC'. 'お' x 100000 . 'XYZ';
       
   373 my @array = $str2 =~ /$Gpad([A-Z]+)/go;
       
   374 print "@array\n"; # "ABC XYZ" と表示される。
       
   375 
       
   376 外字の変換
       
   377 ベンダ定義文字やユーザ定義文字を含む文字列を他の環境でも利用できるようにするには、適切な変換が必要です(無論、まったく同じ字体の利用はほとんど望めず、おそらくは類似した文字や文字列に変換することになるでしょう)。これはPerlでは置換演算子 s/// を使えば比較的容易に実現できます。
       
   378 あらかじめ、どの外字をどう変換するかを定義する変換テーブルを用意しなくてはなりません。これはPerlではハッシュにしておくとその後の処理が楽になります。ここでは、'w932_gai.txt'で定義する、Windows codepage-932コードに基づいた機種依存文字の(部分的)変換テーブルを使うことにします。
       
   379 次のコードでは、1文字づつマッチさせ、その文字が変換ハッシュのキーにあれば対応する値の文字列に置換し、そうでなければそのまま残します。
       
   380 サンプルコード 
       
   381 require 'w932_gai.txt'; # %tableの定義(不完全!)
       
   382 
       
   383 $char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   384 
       
   385 $str =~ s/($char)/exists $table{$1} ? $table{$1} : $1/geo;
       
   386 
       
   387 同様な処理は、つぎのような書き方でもできますが、外字にマッチする正規表現 $gaijiを用意する必要があります。ずれたマッチをしないために、こちらの正規表現には \G が必要です。例えば、$str = '∞@';の後ろ2バイトは "\x87\x40" ですが、こうすればマッチがずれる心配がありません。また、非欲張りマッチ ($char*?)を使えば $char が外字にマッチしないよう変更する必要はありません。
       
   388 サンプルコード 
       
   389 require 'w932_gai.txt'; # %tableの定義(不完全!)
       
   390 
       
   391 $char  = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   392 $gaiji = '(?:[\x87][\x40-\x9c])';
       
   393 
       
   394 $str =~ s/\G($char*?)($gaiji)/$1$table{$2}/g;
       
   395 
       
   396 CP932重複定義文字の変換
       
   397 Microsoft Windows 日本語版で一般的に使用されているコードページ 932 (CP932) では、幾つかの文字が重複して定義された状態になっています。ここで、文字が重複定義されているとは、Unicodeの同じ符号位置に対応付けられていることとします。
       
   398 例えば、CP932 -> Unicode -> CP932 の順で変換されると、重複定義文字は、どれか一つに揃えられます。この優先順位は JIS X 0208, NEC特殊文字 (13区)、IBM拡張文字 (115〜119区)、NEC選定IBM拡張文字 (89〜92区) の順です。一例として、'∵' の場合、NEC特殊文字の "\x87\x9A" や IBM拡張文字の "\xFA\x5B" は、JIS X 0208 の "\x81\xE6" になります。
       
   399 しかし、CP-932 のテキスト中、重複定義文字がどれかに揃っていないことがあります。例えば "\x87\x9A" や "\xFA\x5B" が含まれていると、テキストを目で見ると違いがないのに、"\x81\xE6" で検索しても見つけられないことになります。
       
   400 重複定義文字を揃えるモジュールとして、ShiftJIS/CP932/Correct.pm があります。入手と使い方はPerlのページに戻れば見つかります。
       
   401 また、ShiftJIS/String.pm の strtr() または trclosure() を使う方法もあります。入手と使い方はPerlのページに戻れば見つかります。
       
   402 サンプルコード 
       
   403 
       
   404 # (1) $necJIS -> $jisNEC (9対)
       
   405    $necJIS = "\x87\x90\x87\x91\x87\x92\x87\x95\x87\x96\x87\x97\x87\x9A\x87\x9B\x87\x9C";
       
   406       # NEC特殊文字のうち、JIS文字に変換されるべき非漢字
       
   407    $jisNEC = "\x81\xE0\x81\xDF\x81\xE7\x81\xE3\x81\xDB\x81\xDA\x81\xE6\x81\xBF\x81\xBE";
       
   408       # JIS文字のうち、NEC特殊文字に重複定義されている非漢字
       
   409 
       
   410 # (2) $necibmJIS -> $jisNECIBM (1対)
       
   411    $necibmJIS = "\xEE\xF9";
       
   412       # NEC選定IBM拡張文字のうち、JIS文字に変換されるべき非漢字
       
   413    $jisNECIBM = "\x81\xCA";
       
   414       # JIS文字のうち、NEC選定IBM拡張文字に重複定義されている非漢字
       
   415 
       
   416 # (3) $ibmJIS -> $jisIBM (2対)
       
   417    $ibmJIS = "\xFA\x54\xFA\x5B";
       
   418       # IBM拡張文字のうち、JIS文字に変換されるべき非漢字
       
   419    $jisIBM = "\x81\xCA\x81\xE6";
       
   420       # JIS文字のうち、IBM拡張文字に重複定義されている非漢字
       
   421 
       
   422 # (4) $ibmNEC -> $necIBM (13対)
       
   423    $ibmNEC = "\xFA\x4A-\xFA\x53\xFA\x58\xFA\x59\xFA\x5A";
       
   424       # IBM拡張文字のうち、NEC特殊文字に変換されるべき非漢字
       
   425    $necIBM = "\x87\x54-\x87\x5D\x87\x8A\x87\x82\x87\x84";
       
   426       # NEC特殊文字のうち、IBM拡張文字に重複定義されている非漢字
       
   427 
       
   428 # (5) $necibmIBM -> $ibmNECIBM (13対)
       
   429    $necibmIBM = "\xEE\xEF-\xEE\xF8\xEE\xFA\xEE\xFB\xEE\xFC";
       
   430       # NEC選定IBM拡張文字のうち、IBM拡張文字に変換されるべき非漢字
       
   431    $ibmNECIBM = "\xFA\x40-\xFA\x49\xFA\x55\xFA\x56\xFA\x57";
       
   432       # IBM拡張文字のうち、NEC選定IBM拡張文字に重複定義されている非漢字
       
   433 
       
   434 # (6) $necibmCJK -> $ibmCJK (360対)
       
   435    $necibmCJK = "\xED\x40-\xEE\xEC";
       
   436       # NEC選定IBM拡張文字中の漢字
       
   437    $ibmCJK    = "\xFA\x5C-\xFC\x4B";
       
   438       # IBM拡張文字中の漢字
       
   439 
       
   440 use ShiftJIS::String qw(trclosure);
       
   441 
       
   442 # 変換用クロージャの生成
       
   443 $correctCP932 = trclosure(
       
   444     $necJIS.$necibmJIS.$ibmJIS.$ibmNEC.$necibmIBM.$necibmCJK, # from
       
   445     $jisNEC.$jisNECIBM.$jisIBM.$necIBM.$ibmNECIBM.$ibmCJK     # to
       
   446 );
       
   447 
       
   448 $result = $correctCP932->($source); # $source を変換して $result を得る
       
   449 
       
   450 文字数を数える
       
   451 Shift-JIS文字列の文字数を数えるには、マッチ演算子を利用するならスカラーコンテキストで数えた方が若干速かったです。それより、置換演算子を利用したほうが速く書けるとわかりました。
       
   452 もっともXSで書いたほうがずっと速かったです。まあ、XSUBは無理に利用しなくてもよいでしょう。
       
   453 サンプルコード 
       
   454 use Benchmark;
       
   455 
       
   456 $char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   457 $s = "漢字あ\0AアCテスト -";
       
   458 
       
   459 timethese (100000, {
       
   460   le => q{
       
   461     ($str = $s) =~ s/$char/0/go;
       
   462     $le = length $str;
       
   463   },
       
   464   sg => q{
       
   465     $sg = ($str = $s) =~ s/$char//go;
       
   466   },
       
   467   ab => q{
       
   468     $ab = 0;
       
   469     $ab++ while $s =~ /[^\x81-\x9F\xE0-\xFC]|../g;
       
   470   },
       
   471   ar => q{
       
   472     $ar = @{[ $s =~ /$char/go ]};
       
   473   },
       
   474   gr => q{
       
   475     $gr = grep defined, $s =~ /$char/go;
       
   476   },
       
   477   wh => q{
       
   478     $wh = 0;
       
   479     $wh++ while $s =~ /$char/go;
       
   480   },
       
   481   sj => q{
       
   482     $sj = sjslen($s);
       
   483   },
       
   484   xs => q{
       
   485     $xs = sjlength($s);
       
   486   },
       
   487 });
       
   488 
       
   489 sub sjslen {
       
   490   my($str,$len,$i,$c,$blen);
       
   491   $str = shift;
       
   492   $blen = length $str;
       
   493   while ($i < $blen) {
       
   494     $c = vec($str, $i, 8);
       
   495     if (0x81 <= $c && $c <= 0x9F || 0xE0 <= $c && $c <= 0xFC){ $i++ }
       
   496     $i++,$len++;
       
   497   }
       
   498   $len;
       
   499 }
       
   500 
       
   501 結果 
       
   502 Benchmark: timing 100000 iterations of ab, ar, gr, le, sg, sj, wh, xs...
       
   503     ab:  4 wallclock secs ( 3.46 usr +  0.00 sys =  3.46 CPU) @ 28901.73/s
       
   504     ar:  6 wallclock secs ( 5.98 usr +  0.00 sys =  5.98 CPU) @ 16722.41/s
       
   505     gr:  6 wallclock secs ( 5.50 usr +  0.00 sys =  5.50 CPU) @ 18181.82/s
       
   506     le:  3 wallclock secs ( 2.09 usr +  0.00 sys =  2.09 CPU) @ 47846.89/s
       
   507     sg:  2 wallclock secs ( 1.92 usr +  0.00 sys =  1.92 CPU) @ 52083.33/s
       
   508     sj:  9 wallclock secs ( 8.57 usr +  0.00 sys =  8.57 CPU) @ 11668.61/s
       
   509     wh:  5 wallclock secs ( 4.78 usr +  0.00 sys =  4.78 CPU) @ 20920.50/s
       
   510     xs:  1 wallclock secs ( 0.38 usr +  0.00 sys =  0.38 CPU) @ 263157.89/s
       
   511         (warning: too few iterations for a reliable count)
       
   512 
       
   513 XSUB 
       
   514 int
       
   515 sjlength(arg)
       
   516     SV* arg
       
   517   PROTOTYPE: $
       
   518   PREINIT:
       
   519     unsigned char *str, *p, *e;
       
   520     STRLEN byte, len = 0;
       
   521   CODE:
       
   522     p = str = (unsigned char *)SvPV(arg, byte);
       
   523     e = str + byte;
       
   524     while (p < e) {
       
   525         if (0x81 <= *p && *p <= 0x9F || 0xE0 <= *p && *p <= 0xFC)
       
   526             ++p;
       
   527         ++p, ++len;
       
   528     }
       
   529     RETVAL = len;
       
   530   OUTPUT:
       
   531     RETVAL
       
   532 
       
   533 文字単位に分ける
       
   534 Shift-JIS文字列を文字単位に分割しましょう。この場合は、XSを利用してもあまり速くなりませんでした。返り値のリストを用意するのに時間が取られるのか、やはりPerlの正規表現の処理はかなり速いものだということでしょう。
       
   535 サンプルコード 
       
   536 use Benchmark;
       
   537 
       
   538 $char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   539 $s = "日本語ニホンゴ\0ABC" x 100;
       
   540 
       
   541 timethese (1000, {
       
   542    re => q{
       
   543       @re = $s =~ /$char/go;
       
   544    },
       
   545    xs => q{
       
   546       @xs = sjsplit($s);
       
   547    },
       
   548 });
       
   549 
       
   550 結果 
       
   551 Benchmark: timing 1000 iterations of re, xs...
       
   552     re:  7 wallclock secs ( 6.65 usr +  0.00 sys =  6.65 CPU) @ 150.38/s
       
   553     xs:  6 wallclock secs ( 5.33 usr +  0.00 sys =  5.33 CPU) @ 187.62/s
       
   554 
       
   555 XSUB 
       
   556 void
       
   557 sjsplit(arg)
       
   558     SV* arg
       
   559   PROTOTYPE: $
       
   560   PREINIT:
       
   561     unsigned char *str, *p, *e;
       
   562     STRLEN ch, byte, len = 0;
       
   563   PPCODE:
       
   564     str = (unsigned char *)SvPV(arg,byte);
       
   565     e = str + byte;
       
   566     for (p = str; p < e; p++) {
       
   567         if (0x81 <= *p && *p <= 0x9F || 0xE0 <= *p && *p <= 0xFC) ++p;
       
   568         ++len;
       
   569     }
       
   570     EXTEND(SP,len);
       
   571     for (p = str; p < e; p += ch) {
       
   572         ch = (0x81 <= *p && *p <= 0x9F || 0xE0 < *p && *p <= 0xFC) ? 2 : 1;
       
   573         PUSHs(sv_2mortal(newSVpv(p,ch)));
       
   574     }
       
   575 
       
   576 色々な分割
       
   577 文字で分割でみたように、文字列を分割するには、m//gが便利です。
       
   578 サンプルコード 
       
   579 $onebyte = '[\x00-\x7F\xA1-\xDF]';
       
   580 $twobyte = '(?:[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])'; 
       
   581 $char    = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   582 
       
   583 #1バイト文字の塊と2バイト文字の塊に分ける。
       
   584    while ($str =~ /\G($onebyte*)($twobyte*)/g) {
       
   585       push @one, $1 if $1 ne '';
       
   586       push @two, $2 if $2 ne '';
       
   587    }
       
   588 
       
   589 #句点が最後の文字となるように分割する。
       
   590 # '。' ではいいが、文字によっては注意が必要。
       
   591    @sentences = $str =~ /\G$char*?(?:。|.|$)/g; 
       
   592 
       
   593 特定の長さで切りそろえる
       
   594 長い文字列を特定の長さ(バイト長)で切りそろえるなら、次のようにしてできます。
       
   595 サンプルコード 
       
   596 $char = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   597 
       
   598 $str = 'わざわざEUC-JPに変換しないで、Shift-JISのまま処理'.
       
   599        'できたらいいんだけど、なかなか面倒だねえ。';
       
   600 
       
   601 print join "\n", bytebreak($str,15);
       
   602 
       
   603 sub bytebreak{
       
   604    my($byte,$bmax,$ch,@lines);
       
   605    my $str = shift;
       
   606    $byte = $bmax = shift;
       
   607    foreach $ch ($str =~ /$char/go) {
       
   608       $byte += length $ch;  # 次の文字を継ぎ足した長さ
       
   609       if ($byte <= $bmax) {
       
   610          $lines[-1] .= $ch; # 長すぎなければ継ぎ足す
       
   611       } else {
       
   612          $byte = length $ch;
       
   613          push @lines, $ch;  # さもなければ次の行へ
       
   614       }
       
   615    }
       
   616    return @lines;
       
   617   # 長さが足らない場合に、右をスペースで埋めたければ。
       
   618   # return map {$_ .= ' ' x ($bmax - length)} @lines;
       
   619 }
       
   620 
       
   621 禁則処理は、例えば次のようにして行います。単純な考え方では、禁則処理は、(i) 行頭禁則文字の直前で改行しない;(ii) 行末禁則文字の直後で改行しない;ということになります。また、"(a)"のように、行末禁則文字と行頭禁則文字の間に1文字しかない連続した部分は、その部分の全体が無改行になる点にも配慮します。
       
   622 この例では文字列の長さをバイト長 length で規定していますが、文字幅とバイト数は必ずしも比例しませんので、場合によっては(ギリシア文字は半角幅にしたいとか、またはプロポーショナルの場合とか、UTF-8の場合とか)文字幅を返す width のような関数を定義する必要があるでしょう。
       
   623 また、この例のやり方では、禁則による無改行部分だけで一行より長くなる場合は、はみだしを防げません。それが困るなら、禁則の例外として行を分ける(例えば$nextの長さが$bmaxを超えないようにする)処置が必要でしょう。
       
   624 サンプルコード 
       
   625 $CharRE = '(?:[\x00-\x7F\xA1-\xDF]|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC])';
       
   626 
       
   627 # 行頭禁則文字(一部分)
       
   628 $NotAtBegin = q/)]}’”」』)]}!,.:;?、。々゛゜!,.:;?/;
       
   629 # 行末禁則文字(一部分)
       
   630 $NotAtEnd   = q/([{‘“「『([{/;
       
   631 
       
   632 # ハッシュを作る
       
   633 @NotAtBegin{$NotAtBegin =~ m/$CharRE/g} = ();
       
   634 @NotAtEnd{  $NotAtEnd   =~ m/$CharRE/g} = ();
       
   635 
       
   636 $Str = 'わざわざEUC-JPに変換しないで、Shift-JISのまま処理'.
       
   637        'できたらいいんだけど、なかなか面倒だねえ。';
       
   638 
       
   639 print join "\n", linebreak($Str,16);
       
   640 
       
   641 sub linebreak{
       
   642    my($byte,$i,@chars,$next,@lines);
       
   643    my($str, $bmax, $pad) = @_;
       
   644 
       
   645    # $byteは次の文字を継ぎ足したときの長さ
       
   646    $byte = $bmax; # すぐ改行できるための初期値。
       
   647 
       
   648    # 文字単位にばらす
       
   649    @chars = $str =~ /$CharRE/go; 
       
   650 
       
   651    for ($i=0; $i<@chars; $i++) {
       
   652       $next .= $chars[$i];         # 次の文字
       
   653       $byte += length $chars[$i];  # 次の文字を継ぎ足した長さ
       
   654 
       
   655       # 次の文字が行末禁則のとき
       
   656       next if $i+1 < @chars && exists $NotAtEnd{ $chars[$i] };
       
   657       # 次の文字の次が行頭禁則のとき
       
   658       next if $i+1 < @chars && exists $NotAtBegin{ $chars[$i+1] };
       
   659 
       
   660       # 行の振り分け
       
   661      # 長すぎなければ継ぎ足す
       
   662       if ($byte <= $bmax) {
       
   663          $lines[-1] .= $next;
       
   664       }
       
   665      # さもなければ次の行へ
       
   666       else {
       
   667          push @lines, $next;
       
   668          $byte = length $next;# 新しい行の長さ
       
   669       }
       
   670       $next = '';
       
   671    }
       
   672    return defined $pad && 1 == length $pad # 詰め物
       
   673     ? map {$_ .= $pad x ($bmax - length)} @lines
       
   674     : @lines;
       
   675 }
       
   676 
       
   677 ぶら下がり禁則の場合($bmin から $bmaxの範囲を許す)。 
       
   678       $bmin = $bmax - 2; # 例えば。
       
   679 
       
   680       # 行の振り分け
       
   681      # 長すぎなければ継ぎ足す
       
   682       if ($byte <= $bmax && @lines && length $lines[-1] < $bmin){
       
   683          $lines[-1] .= $next;
       
   684       }
       
   685      # さもなければ次の行へ
       
   686       else {
       
   687          push @lines, $next;
       
   688          $byte = length $next;# 新しい行の長さ
       
   689       }
       
   690 
       
   691 日本語文字列を並び替える
       
   692 仮名文字列を五十音順にソートするモジュールとして、ShiftJIS/Collate.pm があります。入手と使い方はPerlのページに戻れば見つかります。
       
   693 「読み・表記照合」は次のようにして行います。sortYomiメソッドの受け取るリストの各要素は、[ 表記列, 読み列 ]という配列リファレンスでなければなりません。
       
   694 サンプルコード 
       
   695 use ShiftJIS::Collate;
       
   696 
       
   697 my @data = (
       
   698   [qw/ 小山 こやま /],
       
   699   [qw/ 長田 ながた /],
       
   700   [qw/ 田中 たなか /],
       
   701   [qw/ 鈴木 すずき /],
       
   702   [qw/ 小嶋 こじま /],
       
   703   [qw/ 児島 こじま /],
       
   704   [qw/ 小山 おやま /],
       
   705   [qw/ 小島 こじま /],
       
   706   [qw/ 小島 こじま /],
       
   707   [qw/ 山田 やまだ /],
       
   708   [qw/ 永田 ながた /],
       
   709 );
       
   710 
       
   711 @sort = ShiftJIS::Collate->new()->sortYomi(@data);
       
   712 
       
   713 「簡易代表読み照合」は次のようにして行います。sortDaihyoメソッドの受け取るリストの各要素は、[ 表記列, 読み列 ]という配列リファレンスでなければなりません。
       
   714 サンプルコード 
       
   715 
       
   716 #!perl
       
   717 use ShiftJIS::Collate;
       
   718 
       
   719 my @data = (
       
   720   [qw/ λ計算   らむだけいさん /],
       
   721   [qw/ JIS番号  じすばんごう   /],
       
   722   [qw/ 安達     あだち         /],
       
   723   [qw/ 安藤     あんどう       /],
       
   724   [qw/ 河西     かさい         /],
       
   725   [qw/ 河内     かわち         /],
       
   726   [qw/ 角田     かくた         /],
       
   727   [qw/ 角田     かどた         /],
       
   728   [qw/ 如月     きさらぎ       /],
       
   729   [qw/ 河内     こうち         /],
       
   730   [qw/ 幸山     こうやま       /],
       
   731   [qw/ 幸山     さきやま       /],
       
   732   [qw/ 佐藤     さとう         /],
       
   733   [qw/ 佐和田   さわだ         /],
       
   734   [qw/ 沢島     さわしま       /],
       
   735   [qw/ 沢田     さわだ         /],
       
   736   [qw/ 澤田     さわだ         /],
       
   737   [qw/ 角田     つのだ         /],
       
   738   [qw/ 槌井     つちい         /],
       
   739   [qw/ 土井     つちい         /],
       
   740   [qw/ 土居     つちい         /],
       
   741   [qw/ 戸井     とい           /],
       
   742   [qw/ 戸田     とだ           /],
       
   743   [qw/ 土井     どい           /],
       
   744   [qw/ 土居     どい           /],
       
   745   [qw/ 土岐     とき           /],
       
   746   [qw/ 安田     やすだ         /],
       
   747 );
       
   748 
       
   749 @sort = ShiftJIS::Collate->new()->sortDaihyo(@data);
       
   750 
       
   751 
       
   752 Shift-JISの漢字を含むファイル名/パス名
       
   753 本項目は、他の項目に増して、検討不充分のまま記述していますので、もし何か参考にしようと思った場合、十分に注意の上、納得できるまでご自分の作業環境でテストしてください。
       
   754 Windows (95/98/NT/2000など) で、ファイル名やパス名が漢字(ここでは二バイト文字の意味で使っていますので、平仮名や記号なども含みます。)を含む場合、Perlで扱う際に問題が生じる可能性があります。
       
   755 末尾バイトが "\x5C" の漢字をもつファイル名/パス名
       
   756 ディレクトリ操作関数(mkdir, rmdir, opendir, -d など)、ファイル操作関数(open, unlink, -f など)で、アクセスできないことがあります。
       
   757 ファイルの場合は、末尾に半角スペースを添えるとアクセスできる場合があります(例えば、-f '表 ' または -f "\x95\x5C\x20" など)。
       
   758 ディレクトリの場合は、末尾に / か \ を添えるとアクセスできる場合があります(例えば、-d '表/' または -d "\x95\x5C/" など)。末尾に添える文字を半角スペースとしても、うまくアクセスできる場合があります。添える文字の候補として、三種類の文字(スラッシュ、円記号、空白)を挙げましたが、どの文字がよいかは、関数によって異なる場合があるようです。使用する前に十分にテストしてください。
       
   759 なお、ディレクトリ名の末尾に / か \ を添える場合、もともと末尾に / か \ が付いている場合には、二重に付けるとうまく行かないおそれがありますので、文字列連結の前に検査したほうがよいでしょう。
       
   760 どうしても挙動が不明で信頼できない場合は、`` または qx// や system()関数などを通じてWindowsのコマンドを呼ぶのが良いと思います。
       
   761 おまけ
       
   762  Shift-JIS で書かれた POD を Perl 5.8.1, 5.8.2 の Pod::Html で HTML に変換した場合、アンカーの名前は、 英数字と仮名文字 [0xA6..0xDF] を除き、 他の各バイトは下線('_')に変換されるようです。 具体的には、use locale; 下で、lc と s/\W/_/g を実行した結果 (cf. Pod::Html::anchorify) になります。 
       
   763 [2003-11-18]
       
   764 Perlのページ