Tcl – バイナリデータの操作(3) -binary scan-

binary scan

[書式]

binary scan string formatString ?varName varName ...?

binary scanは、formatStringで指定した書式に従ってstringで指定したバイナリデータを文字列に変換を行い、変換した文字列をvarNameへ格納します。戻り値は変換した個数を返します。

stringは、1文字につき1バイト、1バイトとして表現できない文字の上位ビットは切り捨てられます。

formatStringの指定方法

例.

set bin [binary format a* abcdefghi]
=> abcdefghi 相当のバイナリ値が変数binに格納されます。

binary scan $bin HH2H4H6H* n1 n2 n3 n4 n5
=> 5 変換した個数を返します。

puts "$n1 $n2 $n3 $n4 $n5"
=> 6 62 6364 656667 6869
      b  c d  e f g  h i

フィールドは、H, H2, H4, H6 H* の5つあります。変換した値を格納するvarNameもフィールドの個数だけ必要になります。

フィールドの最初の単一文字は変換方法を示すタイプで、その後に続く数字は変換を繰り返す回数(count)を表します。数字の代わりに”*”を使うと、対応するargの文字列すべて変換します。

タイプ”H”は、変数binのバイナリデータをバイト内で、上位から下位の順番で16進数表記の文字列に変換します。countを省略すると、1桁だけ変換されます。

タイプに指定できる種類は以下のようなものがあります。


a
バイナリデータを文字列に変換します。

全てのバイトは、\u0000-\u00ffの範囲の文字として解釈される為、stringで指定したデータが、バイナリ文字列であっても、ISO 8859-1でエンコードされた文字列でもない場合は、encoding convertfromコマンドが必要になります。

例1.

binary scan \x48\x65\x6c\x6c\x6f\x21 a* str1
=> 1
puts $str1
=> Hello!

例2.

binary scan \xe3\x83\x90\xe3\x82\xa4\xe3\x83\x90\xe3\x82\xa4 a* str1
=> 1
puts $str1
=> 文字化けする。
puts [encoding convertfrom utf-8 $str1]
=> バイバイ

[参考]
*ISO 8859-1(Latin-1)
ASCII文字コードに西ヨーロッパ諸国で使われるラテン文字を追加した文字集合。
https://ja.wikipedia.org/wiki/ISO/IEC_8859-1
http://e-words.jp/w/ISO-IEC_8859.html


A
aと同じですが、末尾の空白とNULLが変数に格納される前に削除されます。

例.

binary scan "\x61\x62\x63\x20\x64\x65  \x66  \x00" A* var1
=> 1
puts $var1
=> abc de  f 末尾の空白とNULL(\x00)は削除されます。

b
バイナリデータをlow-to-high順で2進数表記の文字列に変換します。

stringで指定したバイナリデータはバイト単位でfirst-to-lastの順番でスキャンされ、バイト内のビットはlow-to-high順でcountビット分、2進数表記の文字列で出力します。 countが*の場合、stringの残りのすべてのビットがスキャンされます。 countが省略された場合、1ビットがスキャンされます。

例.

binary scan \x07\x87\x05 b5b* var1 var2
=> 2
puts $var1
=> 11100
puts $var2
=> 1110000110100000

[参考]

     0x07        0x87       0x05
  0000 0111  10000 0111  0000 0101
    -------  ---------------------
      ↓               ↓
     var1             var2

var1
low-to-high順で5ビット分格納される。
var2
0x87,0x05の順でスキャンされ、バイト内のビットは、low-to-high順でvar2に格納される。


B
bと同じですが、各バイト内のビットを、high-to-lowの順で2進数表記の文字列で出力します。

例.

binary scan \x70\x87\x05 B5B* var1 var2
=>2
puts $var1
=> 01110
puts $var2
=>1000011100000101

[参考]

     0x70        0x87       0x05
  0111 000  10000 0111  0000 0101
  ------    ---------------------
    ↓                ↓
   var1              var2

h
バイナリデータをlow-to-high順で16進数表記の文字列に変換します。

stringで指定したバイナリデータはバイト単位でfirst-to-lastの順番でスキャンされ、バイト内のデータはlow-to-high順で16進数表記の文字列で出力します。 countが*の場合、stringの残りのすべてのデータがスキャンされます。 countが省略された場合、1桁(4bit)がスキャンされます。

例.

binary scan \x07\x86\x05\x12\x34 h3h* var1 var2
=> 2
puts $var1
=> 706
puts $var2
=> 502143

[参考]

    0x07       0x86       0x05       0x12       0x34
  0000 0111  1000 0110  0000 0101  0001 0010  0011 0100
  ---- ----       ----  ---- ----  ---- ----  ---- ----
    0    7          6     0    5     1    2     3    4
    ------------------   ------------------------------
             ↓                         ↓
    var1 => 706         var2 => 502143

H
hと同じですが、各バイト内のデータを、high-to-lowの順で16進数表記の文字列で出力します。

例.

binary scan \x07\xC6\x05\x1f\x34 H3H* var1 var2
=> 2
puts $var1
=> 07c
puts $var2
=>051f34

[参考]

    0x07       0xc6       0x05       0x1f       0x34
  0000 0111  1100 0110  0000 0101  0001 1111  0011 0100
  ---- ----  ----       ---- ----  ---- ----  ---- ----
    0    7     c          0    5     1    f     3    4
   --------------        ------------------------------
          ↓                            ↓
   var1 => 07c                   var2 => 051f34

c
バイナリデータを8ビット符号付き整数に変換します。変換したデータはリストとして対応する変数に格納されます。

countが*の場合、バイナリデータの残りすべてのバイトがスキャンされます。countが省略された場合、1つの8ビット整数がスキャンされます。

例.

binary scan \x07\x86\x05 c2c* var1 var2
=> 2
puts $var1
=> 7 -122
puts $var2
=>5

変換したデータは符号付きの整数値ですが、以下のような式を使用して符号なし8ビット整数に変換できます。

expr {-122 & 0xff}
=> 134 は16進数で0x86です。

s
バイナリデータをリトルエンディアン(little-endian)のバイト順で表される16ビット符号付き整数に変換します。変換したデータはリストとして対応する変数に格納されます。

countが*の場合、バイナリデータの残りすべてのバイトがスキャンされます。countが省略された場合、1つの16ビット整数がスキャンされます。

例.

binary scan \x05\x00\x07\x00\xf0\xff s2s* var1 var2
=> 2
puts $var1
=> 5 7
puts $var2
=> -16

変換したデータは符号付きの整数値ですが、以下のような式を使用して符号なし16ビット整数に変換できます。

expr {-16 & 0xffff}
=> 65520 は16進数で0xfff0です。

S
バイナリデータをビッグエンディアン(big-endian)のバイト順で表される16ビット符号付き整数に変換します。それ以外は、sと同じです。

例.

binary scan \x00\x05\x00\x07\xff\xf0 S2S* var1 var2
=> 2
puts $var1
=> 5 7
puts $var2
=> -16

t
バイナリデータをTclスクリプトが実行されているマシンのネイティブのバイト順で表されるで16ビット符号付き整数として解釈されます。それ以外の点は、sおよびSと同じです。 マシンのネイティブのバイト順が何であるかを判断するには、tcl_platform配列のbyteOrder要素を参照してください。

例.

puts $tcl_platform(byteOrder)
=> littleEndian

i
バイナリデータをリトルエンディアン(little-endian)のバイト順で表される32ビット符号付き整数に変換します。変換したデータはリストとして対応する変数に格納されます。

countが*の場合、バイナリデータの残りすべてのバイトがスキャンされます。countが省略された場合、1つの32ビット整数がスキャンされます。

例.

set str \x05\x00\x00\x00\x07\x00\x00\x00\xf0\xff\xff\xff
=> ðÿÿÿ
binary scan $str i2i* var1 var2
=> 2
puts $var1
=> 5 7
puts $var2
=> -16

変換したデータは符号付きの整数値ですが、以下のような式を使用して符号なし16ビット整数に変換できます。

  expr {-16 & 0xffffffff}
  => 4294967280 は16進数で0xfffffff0です。

I
バイナリデータをビッグエンディアン(big-endian)のバイト順で表される32ビット符号付き整数に変換します。それ以外は、iと同じです。

例.

set str \x00\x00\x00\x05\x00\x00\x00\x07\xff\xff\xff\xf0
=> ÿÿÿð
binary scan $str I2I* var1 var2
=> 2
puts $var1
=> 5 7
puts $var2
=> -16

n
バイナリデータをTclスクリプトが実行されているマシンのネイティブのバイト順で表されるで32ビット符号付き整数として解釈されます。それ以外の点は、iおよびIと同じです。 マシンのネイティブのバイト順が何であるかを判断するには、tcl_platform配列のbyteOrder要素を参照してください。

例.

puts $tcl_platform(byteOrder)
=> littleEndian

w
バイナリデータをリトルエンディアン(little-endian)のバイト順で表される64ビット符号付き整数に変換します。変換したデータはリストとして対応する変数に格納されます。

countが*の場合、バイナリデータの残りすべてのバイトがスキャンされます。countが省略された場合、1つの64ビット整数がスキャンされます。

例.

set str \x05\x00\x00\x00\x07\x00\x00\x00\xf0\xff\xff\xff
=> ðÿÿÿ
binary scan $str wi* var1 var2
=> 2
puts $var1
=> 30064771077
puts $var2
=> -16

W
バイナリデータをビッグエンディアン(big-endian)のバイト順で表される64ビット符号付き整数に変換します。それ以外は、wと同じです。

例.

set str \x00\x00\x00\x05\x00\x00\x00\x07\xff\xff\xff\xf0
=> ÿÿÿð
binary scan $str WI* var1 var2
=> 2
puts $var1
=> 21474836487
puts $var2
=> -16

m
バイナリデータをTclスクリプトが実行されているマシンのネイティブのバイト順で表されるで64ビット符号付き整数として解釈されます。それ以外の点は、wおよびWと同じです。 マシンのネイティブのバイト順が何であるかを判断するには、tcl_platform配列のbyteOrder要素を参照してください。

例.

puts $tcl_platform(byteOrder)
=> littleEndian

f
バイナリデータをマシンのネイティブ表現における単精度浮動小数点数として解釈されます。浮動小数点数は対応する変数にリストとして格納されます。

countが*の場合、バイナリデータの残りすべてのバイトがスキャンされます。countが省略された場合、1つの単精度浮動小数点数がスキャンされます。

浮動小数点数のサイズはアーキテクチャによって異なりますので、スキャンされるバイト数は異なります。 データが有効な浮動小数点数を表していない場合、結果の値は未定義でコンパイラに依存します。

たとえば、Intel(R) Core(TM) i7-4770 CPU上で実行されているWindowsシステムでは、

binary scan \xcd\xcc\xcc\x3f f var1
=> 1
puts $var1
=> 1.600000023841858 を返します。

r
バイナリデータを単精度浮動小数点数としてリトルエンディアン(little-endian)順に解釈されることを除いてfと同じです。

この変換は、IEEE浮動小数点表現を使用しないシステムに移植することはできません。


R
バイナリデータを単精度浮動小数点数としてビッグエンディアン(big-endian)順に解釈されることを除いてfと同じです。

この変換は、IEEE浮動小数点表現を使用しないシステムに移植することはできません。


d
バイナリデータを倍精度浮動小数点数として解釈されることを除いてfと同じです。

たとえば、Intel(R) Core(TM) i7-4770 CPU上で実行されているWindowsシステムでは、

binary scan \x9a\x99\x99\x99\x99\x99\xf9\x3f d var1
=> 1
puts $var1
=> 1.6 を返します。

q
バイナリデータを倍精度浮動小数点数としてリトルエンディアン(little-endian)順に解釈されることを除いてdと同じです。

この変換は、IEEE浮動小数点表現を使用しないシステムに移植することはできません。


Q
バイナリデータを倍精度浮動小数点数としてビッグエンディアン(big-endian)順に解釈されることを除いてdと同じです。

この変換は、IEEE浮動小数点表現を使用しないシステムに移植することはできません。


x
指定したバイトデータのカーソル位置をcountバイトだけ進めます。countが*または現在のカーソル位置の後のバイト数より大きい場合、カーソルはバイトデータの最後のバイトの後に配置されます。countが省略された場合、カーソルは1バイト進められます。

この型は引数を消費しないことに注意してください。例えば、

binary scan \x01\x02\x03\x04 x2H* var1
=> 1
puts $var1
=> 0304 がvar1に格納されます。

X
指定したバイトデータのカーソル位置をcountバイトだけ後ろに移動します。countが*または現在のカーソル位置よりも大きい場合、カーソルは位置0に配置されるので、次にスキャンされるバイトは文字列の最初のバイトになります。 countが省略された場合、カーソルは1バイト戻ります。

この型は引数を消費しないことに注意してください。 例えば、

binary scan \x01\x02\x03\x04 c2XH* var1 var2
=> 2
puts $var1
=> 1 2 がvar1に格納されます。
puts $var2
=> 020304 がvar2に格納されます。

@
stringで指定されたバイナリデータ内のカーソル位置をcountで指定した絶対位置に移動する。位置0はstringの最初のバイトを表します。countが文字列の末尾を超えた位置を指す場合、カーソルは最後のバイトの後に配置されます。 countを省略すると、エラーが発生します。

例.

binary scan \x01\x02\x03\x04 c2@1H* var1 var2
=> 2
puts $var1
=> 1 2
puts $var2
=> 020304

16進ダンプ

以下のプログラムは、binary scanを使用した、16進ダンプのプログラムです。

[サンプル] hexdump.tcl

#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

# 16進ダンプ

proc hexdump {fname} {
    set addr 0    ;# アドレス
    set fid [open $fname {RDONLY BINARY}]
    while {![eof $fid]} {
        set str ""    ;# 16バイト毎に初期化
        for {set i 0} {$i < 16} {incr i} {
            set byte [read $fid 1]
            if {![eof $fid]} {
                binary scan $byte H* hex
                lappend str $hex
            } else {
                break    ;# forループから抜ける
            }
        }
        puts [format "%06X  %s" $addr $str]
        incr addr 16
    }
    close $fid
}

# main
set fname [lindex $argv 0]    ;# ファイル名の取得
hexdump $fname

[実行例]

$ ./hexdump.tcl str.txt
000000  48 65 6c 6c 6f 21 20 48 65 6c 6c 6f 21 0a 47 6f
000010  6f 64 20 6d 6f 72 6e 69 6e 67 21 0a 42 79 65 20
000020  62 79 65 21 0a 48 61 76 65 20 61 20 6e 69 63 65
000030  20 64 61 79 21 0a e3 81 93 e3 82 93 e3 81 ab e3
000040  81 a1 e3 82 8f 0a e3 81 8a e3 81 af e3 82 88 e3
000050  81 86 e3 81 94 e3 81 96 e3 81 84 e3 81 be e3 81
000060  99 e3 80 82 0a e3 83 90 e3 82 a4 e3 83 90 e3 82
000070  a4 0a e3 82 88 e3 81 84 31 e6 97 a5 e3 82 92 ef
000080  bc 81 0a

文字列操作など、色々説明していない事があるので、単純に1バイトずつ読み込んで16進表記で表示するようにしました。

16バイトずつ読み込んでから、文字列を整形したほうが効率がいいと思います。

[参考]
Dump a file in hex and ASCII


コメント

タイトルとURLをコピーしました