まず、バイナリファイルを扱う際に必要となる基礎知識についておさらいします。すべてのファイルは、テキストファイル か バイナリファイル のどちらかに分類されます。
ファイルタイプ | 説明 |
---|---|
テキストファイル | Windows のメモ帳など、通常のテキストエディタで読み書きできるファイルです。文字、改行、タブ文字などから構成されます。拡張子 .txt .htm .css .cgi .pl などのファイルがこれに分類されます。 |
バイナリファイル | 画像ファイルやワープロのファイルなど、文字や改行などの特殊文字コード以外の情報を含んでいるファイルです。拡張子 .gif .jpg .mpg .doc .xls .exe などのファイルがこれに分類されます。 |
テキストファイルで用いられる 改行 を示すコードは OS によって異なります。
OS | 改行コード | ||
---|---|---|---|
文字表現 | コード表現 | 意味表現 | |
Windows | \r\n | 0d 0a | CR LF |
UNIX系 | \n | 0a | LF |
旧 Macintosh | \r | 0d | CR |
Mac OS X からは UNIX 系の OS がベースになっているため、改行コードも \n になりました。CR はキャリッジリターン(復帰)、LF はラインフィード(改行)を意味します。
OS に依存してプログラムを print "Hello\n"; から print "Hello\r\n"; に書き換えなくてもすむように、Windows ではファイルを読み書きする際のモードに、テキストモード と バイナリモード という機能をサポートしています。
モード | 説明 |
---|---|
テキストモード | テキストファイルの読み書きに適したモードです。書き込み時は \n → \r\n の変換、読み込み時は \r\n → \n の変換が自動的に行われます。このため、print FILE "Hello\n"; としても、ファイルには "Hello\r\n" が書き込まれることになります。 |
バイナリモード | バイナリファイルを読み書きする際に適したモードです。改行コードの自動変換は行われません。 |
標準入出力やオープンしたファイルは通常テキストモードになっています。この状態でバイナリファイルを読み書きすると、0a と 0d 0a の自動変換が働いてしまい、データが壊れてしまいます。
Windows でバイナリファイルを読み込む際には、binmode() を用いて、指定したファイルハンドルをバイナリモードに設定する必要があります。UNIX では binmode() を呼び出しても何の変化もありません。
$size = -s "data.dat"; # ファイルのサイズを得る open(IN, "data.dat"); # ファイルを開く binmode(IN); # バイナリモードにする read(IN, $buf, $size); # データを読み込む close(IN); # クローズする
read() は、ファイルから $size バイト分のデータを読みとって変数 $buf に代入します。成功すると読みこんだバイト数を、さもなくば未定義値を返します。ファイルの終端に達すると 0 を返します。
pack() は、数値や文字列を書式に従ってバイナリデータに詰めこみます。"C" の部分は型指定文字と呼ばれます。CCC や C3 は3個の符号無し1バイト値、C* は任意の個数の符号無し1バイト値を意味します。
$bin = pack("CCC", 65, 66, 67); # "ABC" $bin = pack("C3", 65, 66, 67); # "ABC" $bin = pack("C*", 65, 66, 67); # "ABC"
a ヌル文字埋めの1バイト(ascii)。 A スペース文字埋めの1バイト(ascii)。 Z ヌル文字で終わる文字列(zero terminated string)。 b 下位から上位順のバイナリデータ(binary)。 B 上位から下位順のバイナリデータ(binary)。 h 16進文字列(hexadecimal)。(下位4ビットが先) H 16進文字列(hexadecimal)。(上位4ビットが先) c 符号付き1バイト値(char)。 C 符号無し1バイト値(char)。 s 符号付き16ビット整数(short)。 S 符号無し16ビット整数(short)。 i 符号付き整数(int)。 I 符号無し整数(int)。 l 符号付き32ビット整数(long)。 L 符号無し32ビット整数(long)。 n 符号無し16ビット整数。ネットワークバイトオーダ。ビッグエンディアン。 N 符号無し32ビット整数。ネットワークバイトオーダ。ビッグエンディアン。 v 符号無し16ビット整数。リトルエンディアン。 V 符号無し32ビット整数。リトルエンディアン。 q 符号付き64ビット整数。 Q 符号無し64ビット整数。 f 単精度実数(float)。 d 倍制度実数(double)。 p ヌルで終わる文字列へのポインタ(pointer)。 P 構造体へのポインタ(pointer)。 u UUENCODEされた文字列。 U UTF-8形式のUnicode。 w BERエンコードされた整数。 x ヌル文字。 X 1バイト後退。 @ 絶対位置までヌル文字を埋める。
unpack() は、pack() と逆で、バイナリデータ(文字列を含む)を数値の配列に変換します。型指定文字は pack() と同様です。
@xx = unpack("C*", "ABC\x81\x82\x83"); foreach $c (@xx) { print "$c "; } print "\n";
上記では、ABC の文字コードや、\x81 などのデータを数値配列に変換して表示しています。実行結果は次のようになります。
65 66 67 129 130 131
ord(data) は、data の最初の1バイトの文字コードを数値として返します。unpack("C", data) と同じ意味を持ちます。
$bin = "\x01\x02\x03\x04"; for ($i = 0; $i < length($bin); $i++) { $ch = ord(substr($bin, $i, 1)); printf("%02x ", $ch); }
実行結果は次のようになります。
01 02 03 04
バイナリデータを受けとり、その値を16進数でダンプ(データのリスト表示)します。入力データは文字列でも構いません。
open(IN, "data.dat"); # ファイルを開いて binmode(IN); # バイナリモードにして read(IN, $buf, -s "data.dat"); # サイズ分読み込んで close(IN); # クローズ BinaryDump($buf); # データを16進ダンプする # バイナリデータを16進ダンプする sub BinaryDump { local($buf) = @_; local($len, $i); $len = length($buf); for ($i = 0; $i < $len; $i++) { printf("%02x ", ord(substr($buf, $i, 1))); if (($i % 16) == 15) { print "\n"; } } if (($i % 16) != 15) { print "\n"; } }
実行結果の例を以下に示します。
ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 00 48 00 48 00 00 ff fe 00 17 43 72 65 61 74 65 64 20 77 69 74 68 20 54 68 65 20 47 49 4d 50 ff db 00 43 00 08 06 06 07 06 05 08 07 07 07 09 09 08 0a 0c 14 0d 0c 0b 0b 0c 19 12 13 0f 14 1d 1a 1f 1e
32ビットの整数値を32個のフラグ(旗)の集合と見なして、個々のフラグを立てたり降ろしたりすることができます。こうすると、「AフラグとCフラグが立っている」という複数の状態をひとつの変数で表すことができるようになります。
フラグに用いる定数は、1, 2, 4, 8, 16, ... など、2の乗数で定義します。
$A_FLAG = 1; # Aフラグの定義 $B_FLAG = 2; # Bフラグの定義 $C_FLAG = 4; # Cフラグの定義
フラグを立てるには |= 演算子、フラグを降ろすには &= と ~ 演算子、チェックするには & 演算子を用います。
$flag |= $A_FLAG; # フラグを立てる $flag &= ~$A_FLAG; # フラグを降ろす if ($flag & $A_FLAG) { ... } # フラグが立っているかチェックする
フラグを用いたスクリプトの例を示します。
$flag = 0x00000000; $A_FLAG = 1; # Aフラグの定義 $B_FLAG = 2; # Bフラグの定義 $C_FLAG = 4; # Cフラグの定義 $flag |= $A_FLAG; # Aフラグを立てる $flag |= $B_FLAG; # Bフラグを立てる $flag &= ~$B_FLAG; # Bフラグを降ろす if ($flag & $A_FLAG) { print "A\n"; } # Aフラグが立って入れば if ($flag & $B_FLAG) { print "B\n"; } # Bフラグが立って入れば if ($flag & $C_FLAG) { print "C\n"; } # Cフラグが立って入れば
A、B を立てたあと、B を降ろしているので、結果は次のようになります。
A
vec() を用いることで、32ビットよりも長いビット列の操作を行うことができます。vec($bin, offset, bits) は、$bin が、bits ビットのビット列の集合であると見なし、その offset 番目(0 から数える)のビット列の値を示します。代入も参照も可能です。bits には、1, 2, 4, 6, 8, 16, 32, 64 などを指定できます。
$bin = ''; vec($bin, 0, 1) = 1; # 0番目のビットを立てる vec($bin, 33, 1) = 1; # 33番目のビットを立てる for $i (0..49) { # 50個のビットについて if (vec($bin, $i, 1)) { # $i 番目のビットをチェックする print "1"; } else { print "0"; } } print "\n";
結果は次のようになります。
100000000000000000000000000000000100000000000000000
ビットの集合とかいうとちょっと面倒ですが、例えば、文字列 "ABC" は、8ビットのビット列 3個からなる集合で、その 2番目(0 から数えると 1番目)の文字を書きかえるには次のようにします。
$str = 'ABC'; vec($str, 1, 8) = ord('b'); print "$str\n"; # AbC と表示される