Last modified: Sun Oct 3 04:30:10 JST 1999
Perlに用意されているpack、unpackのメモです。
ちなみにRubyの場合、Array#packとString#unpackが同様の機能を持ちます。 さらに、型指定文字列に「m」が追加され、base64への変換・展開が行えます。 (Perlの時の展開の方法は後述)
$out = pack TEMPLATE, LIST
値のリストを受け取って、 それをバイナリデータの構造体にパックしたものを文字列として返します。 TEMPLATEは、型指定文字を並べて、 値の順番と型を示すようにしたものです。
型指定文字には次のようなものがあります。
| 文字 | 意味 |
|---|---|
| a | ASCII文字列、ヌル文字を詰める |
$out = pack "a6", "abcd"; # "abcd\0\0" | |
| A | ASCII文字列、スペースを詰める |
$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 | (機種依存の)単精度浮動小数点数 |
| h | 16進文字列、下位ニブルが先 |
$out = pack "h8", "4a4a4a4a"; # "いい" (EUC-JPの場合) やっぱり「H」と比べて、その違いに気をつけること。 | |
| H | 16進文字列、上位ニブルが先 |
$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」、 | |
| I | 符合無しint値 |
| l | 符合付きlong値 |
| L | 符合無しlong値 |
| n | ビッグエンディアンによるshort値 |
| N | ビッグエンディアンによるlong値 |
| p | 文字列へのポインタ |
|
ヌルバイトで終わっているはず。 | |
| P | 構造体(固定長文字列)へのポインタ |
| s | 符合付きshort値 |
| S | 符合無しshort値 |
| v | リトルエンディアンによるshort値 |
| V | リトルエンディアンによるlong値 |
| u | uuencodeした文字列 |
$out = pack "u", 1; # "!,0`-\n" | |
| w | BER圧縮された整数値。(説明は以下で) |
| x | ヌルバイト |
$out = pack "ccxc", 0x30, 0x31, 0x32, 0x33; # "01\x003" | |
| X | 1バイト後退する |
$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できないのです(?)。
@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して表示、という流れです。
All contents copyright (C) 1998-1999, Catra All rights reserved.