Last Updated: 2002/03/26
Perl で、16進文字列、10進整数、2進文字列、バイナリー文字列の相互変換を行うための、スクリプトメモです。
特に断っていない限り、Perl 5.001 以上で動作します。
hex() 関数は、16進文字列を整数値に、手軽に変換できます。
$num10 = hex("4A"); # $num10 には 74 が入る
$num10 = hex("FFFFFF"); # $num10 には 16777215 が入る
余談ですが、数値リテラル中では 0x に続けて 16 進数で記述することで、数値を表すことができます。
$num10 = 0x4A; # 74 $num10 = 0xFFFFFF; # 16777215
2進文字列はやっかいです。一度バイナリーにパックしてから、2進文字列に変換するというステップを踏みます。
$num2 = unpack("B8", pack("H2", "7F" )); # "01111111"
$num2 = unpack("B16", pack("H*", "7F03")); # "0111111100000011"
何?バイナリにパックしたい? そりゃ pack() でしょう。
$char = pack("H2", "41" ); # "A"
$char = pack("H4", "454A"); # "EJ"
$char = pack("H*", "454A"); # "EJ"
$char = pack("H2H2", "45", "4A"); # "EJ"
CGI でよく使う、URI unescape。エンコードされた文字列 "%83%7E%83P%83l%83R" を "ミケネコ" にデコードしたいときも、上の例どおり pack() を使います。
$char = "%83%7E%83P%83l%83R";
# 上のお手本どおり
$char =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("H2", $1 )/eg;
# しかしどういうわけか、
$char =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C", hex($1) )/eg;
# こちらの方がポピュラーです。
pack("H2") を使えば一撃で変換できるのに、見るからに遅そうな hex() + pack() がよく使われているのはなぜでしょう?ベンチマークをとってみましょう。
Benchmark: timing 5000 iterations of a, b... a: 3 wallclock secs ( 3.24 usr + 0.00 sys = 3.24 CPU) <- pack() b: 6 wallclock secs ( 5.19 usr + 0.00 sys = 5.19 CPU) <- hex()+pack()
およそ2倍弱ほど違います。
ここでも余談を。変数展開が行われるようなケースでは、エスケープシーケンス \x に続けて 16 進数で記述することで、 1 バイトの文字列を表現することができます。
$char = "\x41"; # "A" $char = "\x45\x4A"; # "EJ" $char = "\x454A"; # "E4A" あれれ? \x は、続く 16 進文字を 2 つだけ認識 /[\x41-\x5A]/ # /[A-Z]/ と同値
一度 pack() して、unpack() することもできますが…
$num16 = unpack("H2", pack("C", 59)); # "3b" しかし…
sprintf() の x フォーマットを使えば、より簡単に変換ができます。
$num16 = sprintf("%x", 59); # "3b"
$num16 = sprintf("%X", 59); # "3B"
$num16 = sprintf("%X", 65536); # "10000"
しかし 10 進整数がリストの場合、要素数だけ、%x%x%x.. とフォーマットを指定しなければならず、リストが大きくなると不便になってきます。この場合は、pack() と unpack() の方がスマートに記述できるでしょう。
$num16 = sprintf("%x%x", 59, 256); # "3b100"
$num16 = sprintf("%x%x", 59, 60); # "3b3c"
$num16 = unpack("H4", pack("C2", 59, 60)); # "3b3c"
$num16 = sprintf("%x%x%x", 59, 60, 61); # "3b3c3d"
$num16 = unpack("H*", pack("C*", 59, 60, 61)); # "3b3c3d" スマート?
pack() して、unpack()。やっかいですな。
$num2 = unpack("B8", pack("C", 127)); # "01111111"
$num2 = unpack("b8", pack("C", 127)); # "11111110"
$num2 = unpack("B16", pack("C*", 254, 3)); # "0111111100000011"
1個だけなら chr() が手軽です。
$char = chr(65); # "A"
いくつかあるなら素直に pack()。
$char = pack("C", 65); # "A"
$char = pack("C*", 65, 66, 67, 68, 69); # "ABCDE"
vec() は少し特殊です。左辺値として使い、整数 65 を A にパックし、$char の任意の位置に埋め込むことができます。
$char = ""; vec($char, 0, 8) = 65; # "A" $char = "hello"; vec($char, 1, 8) = 65; # "hAllo"
pack() して、unpack()。
$num10 = unpack("C", pack("B8", "01111111" )); # 127
@num10 = unpack("C*", pack("B16", "0000000101111111" )); # (1, 127)
@num10 = unpack("C*", pack("B8B8", "00000001","01111111")); # (1, 127)
pack のテンプレートを小文字の b にすると、反転したビットを扱います。
$num10 = unpack("C", pack("b8", "01111111")); # 254
Perl 5.6 以上からは、2進数の定数を 0b で表現できるようになりました。
$num10 = 0b01111111; # 127 Perl5.6 以上限定なんだって
pack() して、unpack()。
$num16 = unpack("H2", pack("B8", "01111111" )); # "7f"
$num16 = unpack("H*", pack("B16", "0000000101111111" )); # "017f"
$num16 = unpack("H*", pack("B8B8", "00000001", "01111111")); # "017f"
バイナリパックは、もちろん pack()。pack()は必ずスカラーを返します。
$char = pack("B8", "01000001"); # "A"
$char = pack("B16", "0100000101000010"); # "AB"
$char = pack("B8B8","01000001","01000010"); # "AB"
vec() を使い、$char の任意の位置のビットを書き換える例です。
@bit = qw( 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 );
$char = ""; $i = 0;
foreach (@bit){
vec($char, $i++, 1) = $_;
}
# $char は、"AB" になる
これは簡単。unpack() で一撃です。unpack() はリストを返すことに注意して下さい。
$num16 = unpack("H2", "A"); # "41"
$num16 = unpack("H*", "AB"); # "4142"
$num16 = unpack("H*", "\x41\x42"); # "4142"
@num16 = unpack("H2H2", "\x41\x42"); # ("41", "42")
unpack() を使います。固定した文字列に対して、数箇所のバイトを抽出するときは、vec() が便利です。
$num10 = unpack("C", "A" ); # 69
$num10 = unpack("C", "\x45" ); # 69
$num10 = vec("A", 0, 8); # 69
$num10 = vec("\x45", 0, 8); # 69
$num10 = vec("\x45\x46", 1, 8); # 70 "\x45" はスキップされた
$num10 = ord("A"); # 69 ord() って関数もあったね
長いバイナリを変換する例。
@num10 = unpack("C*", "\x47\x48\x49\x4A"); # (71, 72, 73, 74)
@num10 = unpack("C*", "GHIJ"); # (71, 72, 73, 74)
$num10 = vec("\x00\x00\x00\x01", 0 , 32); # 1
$num10 = vec("\x00\x00\xFF\xFF", 0 , 32); # 65535
$num10 = vec("\x47\x48\x49\x4A", 0 , 32); # 1195919690
$num10 = vec("GHIJ", 0 , 32); # 1195919690
unpack() で一撃です。
$num2 = unpack("B8", "\x45"); # "01000101"
$num2 = unpack("B8", "E" ); # "01000101"
$num2 = unpack("B16", "EF"); # "0100010101000110"
$num2 = unpack("B*", "\x45\x46"); # "0100010101000110"
@num2 = unpack("B8B8", "\x45\x46"); # ("01000101", "01000110")
unpack() が返したスカラー1個を split() で分解し、ビット列のリストにして返します。
@bit = split(//, unpack("B8", "E")); # qw(0 1 0 0 0 1 0 1)
@bit = split(//, unpack("B16","EJ")); # qw(0 1 0 0 0 1 0 1 0 1 0 0 0 1 1 0)
また、上と同じことをしようとして、次のように書いてもうまく動きません。
@bit = unpack("BBBBBBBB", "E")); # qw(0 1 0 0 0 1 0 1) じゃないの?
# 実際には、qw(0 undef undef undef undef undef undef undef) が返る。
最後に
これらの変換が大量に必要になったとき、どうして1回で変換できる関数が Perl に用意されていないのか、どうしてこんなにややこしくて目の疲れるフォーマットで記述しなければならないのか、と憤慨するかもしれません。しかしその前に、自分は Perl に苦手な仕事をさせようとしていないか、ちょっと考えてみて下さい。
一般に、Perl はこういった仕事をたくさんこなすには、不向きなのです。