あるファイルが画像データであるかどうかを判定するために, そのファイルのヘッダ部を正規表現でチェックするという方法がある.
この例では, JPEG 画像ならば /^\xFF\xD8/
,
PNG 画像ならば /^\x89PNG
,
GIF 画像ならば /^GIF8[79]a/
という正規表現によって
チェックを行っている.
では, あまり広く使われていないようなバイナリファイルのヘッダを 正規表現でチェックできるようにするにはどうしたらよいのだろうか?
今回は気象海洋の業界でそこそこ使われている, Unidata で提供される NetCDF 形式データを例に, 上記のようにバイナリデータを正規表現でチェックするように できるまでを記す.
(ちょっと偉そうですが単なる作業ログです ^^;)
まずは調べるべきデータがないとどうにもならないので, NetCDF データを
あちこちからかき集めてくる. データの数はそれなりに多い方がよい.
なぜなら, データによってはマイナーバージョンの違いによって微妙に
ヘッダが異なる場合もあるからである. 今回は nc_data
という
ディレクトリに, sample_001.nc 〜 sample_100.nc という 100 個の
データを集めたことにする.
実際のところはもちろん, 集めたファイルを sample_001.nc なんて名前のファ イルに変更することなどせず, NetCDF データが多く転がってそうなホストに 移動し, 以下のコマンドで拡張子 nc のファイルを片っ端から拾ってきた.
$ locate .nc |grep \\\.nc$
バイナリデータなので, 普通のテキストとしてファイルを開こうとすると 開いてもわけがわからないため, バイナリエディタでファイルを開いてみる.
ここではバイナリエディタとして UNIX 系 OS なら近年は大抵 利用可能な Emacs を使用する. なお, Windows 版の Emacs として meadow というものもある.
まずは emacs を普通に起動する.
$ emacs
起動後, hexl-find-file で nc ファイルを開く.
M-x hexl-find-file
の後 (M-x は Alt-x や Esc x の意味), ファイル名を聞かれるので, nc_data/sample_001.nc を開いてみる. 開いてみると,
00000000: 4344 4601 0000 000b 0000 000a 0000 0003 CDF............. 00000010: 0000 0001 7800 0000 0000 0020 0000 0001 ....x...... .... 00000020: 7900 0000 0000 0020 0000 0001 7400 0000 y...... ....t... 00000030: 0000 0000 0000 000c 0000 0006 0000 000b ................ 00000040: 436f 6e76 656e 7469 6f6e 7300 0000 0002 Conventions.....
のように表示される. 一番左の 8 桁の数字はとりあえず気にしなくて よい. 肝心なのは,
である. 右端の ASCII 文字列で "." (ドット) になっている部分は, ASCII では表示できない文字列である. (ちなみに, これはしょうがないのだが, 実際に ASCII で "." な文字は, 紛らわしいが "." で表される. このドットは 16 進数では "2e" であるため, 中央の文字列と見比べて 判断して欲しい).
さて, 先に nc ファイルを開いてしまったが, nc ファイルでチェックすべき ヘッダ部分を探す前に, 既にチェック方法がわかっている JPEG や PNG, GIF を見てみよう.
先と同様に hexl-find-file で JPEG ファイルを開いてみると
00000000: ffd8 ffe0 0010 4a46 4946 0001 0101 0001 ......JFIF...... 00000010: 0001 0000 ffdb 0043 0008 0606 0706 0508 .......C........ 00000020: 0707 0709 0908 0a0c 140d 0c0b 0b0c 1912 ................
のように表示される. ここで注目すべきは先頭の "ffd8" である.
上記で紹介した JPEG ファイル判定の正規表現は /^\xFF\xD8/
である.
"\x" は "\xnn" という使い方をする 16 進数表記で, n は 0-9,a-f である.
(英字の大文字小文字はここでは区別しない). つまり この判定方法は,
これは上記のバイナリエディタの内容と符合していることがわかる.
PNG ファイルを hexl-find-file で開くと,
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 00000010: 0000 0032 0000 0040 0803 0000 006f 0bd4 ...2...@.....o.. 00000020: 4900 0000 0467 414d 4100 00d6 d8d4 4f58 I....gAMA.....OX
のように表示される. 今回注目したいのは右側のアスキー表示である.
1文字目はバイナリだが, 次の 3 文字は "PNG" というアスキー文字である.
そのため, PNG 画像の判定の正規表現には/^\x89PNG
が用いられている. これは1文字目は "\x89" という
16 進数表記でヒットさせ, 残りの 3 文字は普通にアスキー文字でヒット
させている. ヘッダにアスキー文字があればこのようなことも可能である.
(もちろん "PNG" を "\x50\x4e\x47" としても効果は一緒である).
GIF ファイルを hexl-find-file で開くと,
00000000: 4749 4638 3961 1c02 7a01 f653 0022 2222 GIF89a..z..S.""" 00000010: 2b2b 2b38 3838 7a17 2e61 3079 7127 6341 +++888z..a0yq'cA 00000020: 4141 5f5f 5f6b 5f5f 7746 467c 4b4b 7c4b AA___k__wFF|KK|K
のように表示される. GIF 画像は始めの数文字が全てアスキー文字で表記
されているため, /^GIF8[79]a/
でヒットさせている. "[79]" と
なっているのは, GIF のバージョンによって先頭の文字が "GIF89a" だったり
"GIF87a" であったりするためであろうと考えられる.
GIF 画像はヘッダがこのようになっているためしょうがないことではあるが, 万が一先頭が "GIF89a" で始まるテキストファイルがある場合, そのファイル は GIF 画像として扱われることになる.
さて, NetCDF ファイルの判定の話に戻ろう. 残念ながら, Emacs でファイル を1つ開いただけでは, どこまでが NetCDF ファイルのヘッダ判定に使用でき るのかがわからない. 従って, 集めてみたファイルの先頭部分を片っ端から眺 めてみる. (この方法も, 実はちょっと乱暴かもしれない. 正しくはデータ形 式を策定した組織なり団体のページに行って, 仕様を確認すべきなのかも. で も面倒なのでやりません).
当然ながら Emacs で一個一個開いていくのは手間だし, それでは確認できる ファイルの数も限られるので, コマンドラインでバシバシ表示する. ここでは "od" というコマンドを使用する. od はファイルを 8 進数や 16 進数で表示してくれるコマンドである. 以下のように使用すると, ファイルの先頭を 16 進数表示する.
$ od -x nc_data/sample_001.nc | head -1 0000000 4443 0146 0000 0200 0000 0a00 0000 0400
ここで注意して欲しいのは, emacs で開いたときと, 文字が入れ替わっている ことである. emacs で開いた際には
00000000: 4344 4601 0000 000b 0000 000a 0000 0003 CDF.............
となっていた. つまり, 先頭2文字は "43" と "44" が, 次の 2 文字は "46" と "01" がひっくり返って表示される. これはビッグ/リトルエンディアンに 関する問題で, od を実行しているアーキテクチャの CPU に依存する. Intel 系 CPU はリトルエンディアンなので, このようにひっくり返って表示される. 一方で IBM 系の CPU はビッグエンディアンなので, Emacs の表記と同じよう に表示される.
ともあれ, このコマンドを用いた, 引数に受け取るファイルのバイナリヘッダ を 16 進数でリストするシェルスクリプトを作成する. 以下はそのサンプルである.
#!/bin/sh for ncfile in "$@" ; do echo -n "`od -x $ncfile |head -1`" echo " : `basename $ncfile`" done
このファイルを nc_binary_header_show.sh として保存する. そして nc ファイル群をこのスクリプトの引数に渡す.
$ sh nc_binary_header_show.sh nc_data/sample_*.nc
すると以下のようにバイナリのヘッダがリスト表示される.
0000000 4443 0146 0000 0000 0000 0a00 0000 0300 : sample_001.nc 0000000 4443 0146 0000 c900 0000 0a00 0000 0200 : sample_002.nc 0000000 4443 0146 0000 2000 0000 0a00 0000 0300 : sample_003.nc 0000000 4443 0246 0000 0200 0000 0a00 0000 0400 : sample_004.nc 0000000 4443 0146 0000 0500 0000 0a00 0000 0200 : sample_005.nc 0000000 4443 0146 0000 0b00 0000 0a00 0000 0300 : sample_006.nc 0000000 4443 0246 0000 0200 0000 0a00 0000 0600 : sample_007.nc 0000000 4443 0146 0000 0b00 0000 0a00 0000 0300 : sample_008.nc 0000000 4443 0146 0000 1500 0000 0a00 0000 0300 : sample_009.nc
13 桁の数字 (ASCII 文字で言えば 7 文字目) からはファイルによって 異なるため, これは各ファイルごとのデータの実体部分と想像できる. また 9〜12 桁目の数字はヘッダ部とデータ部とを分ける部分のようである.
従って, 1〜8 桁目の数字 (ASCII 4 文字分) が判定に使用できそうである. ただよく見ると, 6 桁目の数字で "1" であるものと "2" であるものが 存在する. これは NetCDF のバージョン等の違いによるものであると 想像する. (実際には, 32 bit データは "1", 64 bit データは "2" と なっているらしかった).
なので, 先頭が 16 進数表記で "43", "44", "46", "01" (または "02") となっているものを NetCDF データと判定する. ただ emacs の表記を見ると 最初 3 文字は "CDF" という ASCII 文字なので, 普通に ASCII で 判定させてよい.
これらを踏まえ, NetCDF データを判定する正規表現は
/^CDF(\x01|\x02)/
とすることが出来た. 幸いにしてバイナリデータが
含まれるため, テキストが誤認される可能性も小さいと思われる.
Last Updated: 2006/09/04, Since: 2006/08/31 | mailto: |