ISO-2022-JPを扱う際の注意

ISO-2022-JP(いわゆるJIS)で書かれたファイルから特定の文字列を検索する、というのはそれほど簡単な話ではありません。単純に部分一致するバイト列を探すだけではできないからです。

ISO-2022-JPという文字コードは、エスケープシーケンスによって文字セットを切り替えながら文字列を表現します。

どういうことかというと、文字列の中で文字がASCIIから日本語に切り替わるときに、「ここから日本語」と宣言してから日本語を表すバイト列が続きます。逆に日本語からASCIIに切り替わるときに「ここからASCII」と言ってからASCIIの文字列が続きます。IMEで入力する文字を切り替える度に[半角/全角]キーを押すのと似たようなものです。

つまり文字列の各部分において状態を持つのです。
例えば「0x24 0x22」というバイト列は、ASCII状態では「$"」になりますが、日本語状態(JIS X 0208-1983)だと「あ」になります。

これを知っておかないとえらい目に遭います。遭いました。

実験

ちょいと実験してみましょう。

ISO-2022-JP で適当にファイルを作ります。emacs では C-x RET f して iso-2022-jp を指定します。

$ cat hoge.txt
$"$$
$$$+
$$
$ nkf -w hoge.txt
あい
いか
い
$ nkf --guess hoge.txt
ISO-2022-JP

では、これをgrepしてみましょう。検索する語を ISO-2022-JP で指定します。

$ nkf -w grep.sh
#!/bin/sh
grep -n 'い' hoge.txt | nkf -w
$ nkf --guess grep.sh
ISO-2022-JP
$ ./grep.sh
3:い

このように、一行目・二行目の「い」はマッチせず、三行目のみマッチします。

何が起こっているか、ダンプしてみると分かります。
まず検索対象のファイルは

$ od -t xCa hoge.txt
0000000 1b 24 42 24 22 24 24 1b 28 42 0a 1b 24 42 24 24
        esc   $   B   $   "   $   $ esc   (   B  nl esc   $   B   $   $
0000020 24 2b 1b 28 42 0a 1b 24 42 24 24 1b 28 42 0a
          $   + esc   (   B  nl esc   $   B   $   $ esc   (   B  nl
0000037

各行頭に日本語文字セット(JIS X 0208-1983)を表すエスケープシーケンス「esc $ B」が、各行末にASCIIを表すエスケープシーケンス「esc ( B」が入っていることがわかります。
また、JIS X 0208-1983 において、

  • あ = $" [0x24 0x22]
  • い = $$ [0x24 0x24]
  • か = $+ [0x24 0x2b]

なので、このファイルはこんな感じで表されています。

<JIS>あい<ASCII>\n<JIS>いか<ASCII>\n<JIS>い<ASCII>\n
  • = esc $ B [0x14 0x24 0x42]
  • = esc ( B [0x14 0x28 0x42]

です。

次に検索しようとしている文字列のダンプを見てみます。

$ od -t xCa grep.sh
0000000 23 21 2f 62 69 6e 2f 73 68 0a 67 72 65 70 20 2d
          #   !   /   b   i   n   /   s   h  nl   g   r   e   p  sp   -
0000020 6e 20 27 1b 24 42 24 24 1b 28 42 27 20 68 6f 67
          n  sp   ' esc   $   B   $   $ esc   (   B   '  sp   h   o   g
0000040 65 2e 74 78 74 20 7c 20 6e 6b 66 20 2d 77 0a
          e   .   t   x   t  sp   |  sp   n   k   f  sp   -   w  nl
0000057

'い' にあたる部分は、' esc $ B $ $ esc ( B ' となります。つまり '' です。これを先のファイルから探すと3行目のみマッチすることが分かるでしょう。

では検索を正しく実装するにはどうすればいいか。多少ゴミを拾ってもいいんであれば、検索キーから先頭と末尾のエスケープシーケンスを抜いてバイト列の部分マッチを取ればよいでしょう。
そうでなければ愚直に先頭からエスケープシーケンスを探していき、状態を切り替えながら検索するしかなさそうです。

いったんUTF-8とかに変換てしまうのが一番楽かもしれません。

その他Tips

その他この文字コードの特徴としては、それぞれのバイトで 7 ビットしか使わないというのがあります。これが理由でメールに使われたりしてます。

ISO-2022-JPが使い分ける文字セットは全部で4種類あり、エスケープシーケンスとして「esc ( B」と「esc $ B」以外に「esc ( J」や「esc $ @」もあります。

また、文字セットを切り替えまくるような場合は非常に効率が悪くなります。日本語を一文字ずつ半角スペース区切りで n 文字並べると、ファイルサイズは合計で (3 + 2 + 3 + 1) * n = 9n バイトになります。

まとめ

ISO-2022-JPは面倒くさい文字コードです。
特に検索をかけるかもしれないデータを保存する際などには用いるべきではありません。
生のメールデータやIRCのログデータを扱うときは注意しましょう。