バイナリデータ

■ バイナリファイルの基礎知識

◆ テキストファイルとバイナリファイル

まず、バイナリファイルを扱う際に必要となる基礎知識についておさらいします。すべてのファイルは、テキストファイルバイナリファイル のどちらかに分類されます。

ファイルタイプ説明
テキストファイルWindows のメモ帳など、通常のテキストエディタで読み書きできるファイルです。文字、改行、タブ文字などから構成されます。拡張子 .txt .htm .css .cgi .pl などのファイルがこれに分類されます。
バイナリファイル画像ファイルやワープロのファイルなど、文字や改行などの特殊文字コード以外の情報を含んでいるファイルです。拡張子 .gif .jpg .mpg .doc .xls .exe などのファイルがこれに分類されます。
◆ 改行コード

テキストファイルで用いられる 改行 を示すコードは OS によって異なります。

OS改行コード
文字表現コード表現意味表現
Windows\r\n0d 0aCR LF
UNIX系\n0aLF
旧 Macintosh\r0dCR

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" が書き込まれることになります。
バイナリモードバイナリファイルを読み書きする際に適したモードです。改行コードの自動変換は行われません。

■ バイナリファイルを読みこむ(binmode, read)

◆ バイナリファイルを読み込む際の注意

標準入出力やオープンしたファイルは通常テキストモードになっています。この状態でバイナリファイルを読み書きすると、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 を返します。

■ バイナリファイルに書きこむ

◆ バイナリファイルへの書き込み

バイナリファイルに書きこむには、print() を用います。write() という関数もありますが、これは read() に対応するものではありません。バイナリファイルを書き出す際は binmode() でバイナリモードに設定しておいて、print で書き出します。

open(OUT, "> data.dat");
binmode(OUT);
print OUT $bindata;
close(OUT);

■ バイナリデータに詰めこむ(pack)

◆ バイナリデータにパックする

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"
◆ packの型指定文字
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バイト後退。
@   絶対位置までヌル文字を埋める。

■ バイナリデータを解釈する(1)(unpack)

◆ 説明

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

■ バイナリデータを解釈する(2)(ord)

◆ 説明

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進ダンプする(BinaryDump)

◆ 16進ダンプサブルーチン

バイナリデータを受けとり、その値を16進数でダンプ(データのリスト表示)します。入力データは文字列でも構いません。

bindump.pl
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) { ... }   # フラグが立っているかチェックする
◆ フラグ処理の例

フラグを用いたスクリプトの例を示します。

bitflag.pl
$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() を用いることで、32ビットよりも長いビット列の操作を行うことができます。vec($bin, offset, bits) は、$bin が、bits ビットのビット列の集合であると見なし、その offset 番目(0 から数える)のビット列の値を示します。代入も参照も可能です。bits には、1, 2, 4, 6, 8, 16, 32, 64 などを指定できます。

vec.pl
$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 と表示される

Copyright (C) 2002 杜甫々