Last modified: Sun Oct 3 04:30:10 JST 1999

Perlのpackとunpack


Perlに用意されているpack、unpackのメモです。

ちなみにRubyの場合、Array#packとString#unpackが同様の機能を持ちます。 さらに、型指定文字列に「m」が追加され、base64への変換・展開が行えます。 (Perlの時の展開の方法は後述)

pack

$out = pack TEMPLATE, LIST

値のリストを受け取って、 それをバイナリデータの構造体にパックしたものを文字列として返します。 TEMPLATEは、型指定文字を並べて、 値の順番と型を示すようにしたものです。

型指定文字には次のようなものがあります。

文字意味
aASCII文字列、ヌル文字を詰める
$out = pack "a6", "abcd"; # "abcd\0\0"
AASCII文字列、スペースを詰める
$out = pack "A6", "abcd"; # "abcd\x20\x20"
bビットストリング、下位ビットから上位ビットの順(vec()と同じ)
$out = pack "b8", "10001100"; # "1"

「B」と比べて、その違いに気をつけること。

Bビットストリング、上位ビットから下位ビットの順
$out = pack "B8", "00110001"; # "1"
c符合付きchar値
$out = pack "c4", 64, 65, 66, 67; # "ABCD"
C符合無しchar値
$out = pack "C4", 164, 164, 164, 164; # "いい" (EUC-JPの場合)
d(機種依存の)倍精度浮動小数点数
f(機種依存の)単精度浮動小数点数
h16進文字列、下位ニブルが先
$out = pack "h8", "4a4a4a4a"; # "いい" (EUC-JPの場合)

やっぱり「H」と比べて、その違いに気をつけること。

H16進文字列、上位ニブルが先
$out = pack "H8", "a4a4a4a4"; # "いい" (EUC-JPの場合)
i符合付きint値
$out = pack "i2", 1, 2; # "\x01\x00\x00\x00\x02\x00\x00\x00"
(32bit、リトルエンディアンの場合)

「i」、「I」、「l」、「L」、「n」、「N」、「s」、「S」、
「v」、「V」に関しては、後述。

I符合無しint値
l符合付きlong値
L符合無しlong値
nビッグエンディアンによるshort値
Nビッグエンディアンによるlong値
p文字列へのポインタ

ヌルバイトで終わっているはず。

P構造体(固定長文字列)へのポインタ
s符合付きshort値
S符合無しshort値
vリトルエンディアンによるshort値
Vリトルエンディアンによるlong値
uuuencodeした文字列
$out = pack "u", 1; # "!,0`-\n"
wBER圧縮された整数値。(説明は以下で)
xヌルバイト
$out = pack "ccxc", 0x30, 0x31, 0x32, 0x33; # "01\x003"
X1バイト後退する
$out = pack "ccXc", 0x30, 0x31, 0x32, 0x33; # "02"
@絶対位置までヌルバイトを詰める

「i」、「I」、「l」、「L」、「n」、「N」、「s」、「S」、 「v」、「V」のうち、「iIlLsS」はエンディアンが規定されてなく、 さらに「iI」はサイズも実行環境に依存します。 (C言語に置けるint、longなどを考えると良いでしょう。)
「nNvV」はそれぞれ、「nN」がビッグエンディアン(ネットワークバイト順)、 「vV」がリトルエンディアン(VAXバイト順)です。 shortとlongのサイズ規定が無い気がしますが、 多分16ビットと、32ビットだと思います。

「w」はBER圧縮された整数値を返します。 128進数で表され、 上の方から順に出来るだけ少ないバイト数で表現します。 最後のバイトを除いて、MSBにビットフラグが立ちます。

例えばこんな感じです。

pack "w", 4;     # -> 0x04
pack "w", 1024;  # -> 0x8800
pack "w", 30000; # -> 0x81ea30

おおかたの予想通り、普通の整数をサイズを減らして格納できるように、 というのが狙いです。

ちなみに、Rubyのpackでは「w」は使えません(今のところ(1.4.x)では)。 Perlもバージョンが古いと(Perl 5.003か5.004以上かな?)使えません。

「u」(uuencode)を使って、uuencodeが書ける(はずです)。 普通のuuencodeコマンドとは違うけど、簡単な例。

#!/usr/bin/perl

$encode_filename = shift or die "usage: $0 filename";
open(INPUT, $encode_filename) or die "can't open file : $!";

$buff = "";
$out = "";
print "begin 644 $encode_filename\n";
while (read INPUT, $buff, 45) {
    $out = pack "u", $buff;
    print $out;
}
print "end\n";

でも、Debian付属のuudecodeでdecodeできないのです(?)。


unpack

@list = unpack TEMPLATE, EXPR

packの逆の働き −バイナリデータの構造体を含む文字列EXPRを受取り、 それをリスト値に展開して返す− をします。 TEMPLATEはpack関数とほとんど同じ形式です。

pack関数にuuencodeを行う「u」が存在するので、 当然unpackを使ってuudecodeを行うことができます。 (プログラミングPerlより引用 (pp.272))

#!/usr/bin/perl

$_ = <> until ($mode, $file) = /^begin\s*(\d*)\s*(\S*)/;
open(OUT, "> $file") if $file ne "";
while(<>) {
    last if /^end/;
    next if /[a-z]/;
    next unless int((((ord() - 32) & 077) + 2) / 3) == 
	int(length() / 4);
    print OUT unpack "u", $_;
}
chmod oct $mode, $file;

こちらはDebianのuuencodeでencodeしたファイルをdecodeできるようです。

Perlでbase64された文字列をunpackする方法が、 プログラミングPerlの272ページにあります。

tr#A-Za-z0-9+/##cd;
tr#A-Za-z0-9+# -_#;
$len = pack("c", 32 + 0.75*length);
print unpack("u", $len . $_);

base64以外の文字を削除(1行目)、 uuencodeフォーマットに変換、 長さバイトを計算、uudecodeして表示、という流れです。


GFG07377@nifty.ne.jp

All contents copyright (C) 1998-1999, Catra All rights reserved.