2012-08-07 13 views
6

struct.unpack()を使用して、ASCII文字列で終わるデータレコードを分割しようとしています。ASCIIZ文字列で終わる構造体を展開する

レコード(トムトムOV2レコードであることを起こる)、このフォーマットを有する(記憶されたリトルエンディアン):

  • 1バイト(このフィールドを含む)の合計レコード・サイズの
  • 4バイトINT
  • NULL終端さ
  • 4バイトのint
  • 4バイトのint型
  • 可変長文字列、

unpack()は、文字列の長さを渡す形式で含める必要があります。

str_len = struct.unpack("<xi", record[:5])[0] - 13 
fmt = "<biii{0}s".format(str_len) 

その後、フル開梱を進めるが、文字列は、NULLで終了ですので、: - 13バイト - 文字列の長さを得るために、私は2番目のフィールドとレコードの残りの既知のサイズを使用することができます、私は本当にunpack()が私のためにそれをすることを望む。また、独自のサイズを含まない構造体を渡って実行するといいでしょう。

どうすればいいですか?

+0

私はこれに答えてきましたが、私が思いついた解決策を共有するために、私は他の人たちを見て喜んでいます。 –

答えて

5

実際には、struct.calcsize()は予想される長さを示すので、サイズレスのレコードはかなり扱いやすいです。これを使用してデータの実際の長さを計算し、正しい文字列の長さを含むunpack()の新しい書式文字列を作成することができます。

この関数は、端末NULをドロップします最後の位置に新たなフォーマット文字を許可する、unpack()ための単なるラッパーです:

import struct 
def unpack_with_final_asciiz(fmt, dat): 
    """ 
    Unpack binary data, handling a null-terminated string at the end 
    (and only at the end) automatically. 

    The first argument, fmt, is a struct.unpack() format string with the 
    following modfications: 
    If fmt's last character is 'z', the returned string will drop the NUL. 
    If it is 's' with no length, the string including NUL will be returned. 
    If it is 's' with a length, behavior is identical to normal unpack(). 
    """ 
    # Just pass on if no special behavior is required 
    if fmt[-1] not in ('z', 's') or (fmt[-1] == 's' and fmt[-2].isdigit()): 
     return struct.unpack(fmt, dat) 

    # Use format string to get size of contained string and rest of record 
    non_str_len = struct.calcsize(fmt[:-1]) 
    str_len = len(dat) - non_str_len 

    # Set up new format string 
    # If passed 'z', treat terminating NUL as a "pad byte" 
    if fmt[-1] == 'z': 
     str_fmt = "{0}sx".format(str_len - 1) 
    else: 
     str_fmt = "{0}s".format(str_len) 
    new_fmt = fmt[:-1] + str_fmt 

    return struct.unpack(new_fmt, dat) 

>>> dat = b'\x02\x1e\x00\x00\x00z\x8eJ\x00\xb1\x7f\x03\x00Down by the river\x00' 
>>> unpack_with_final_asciiz("<biiiz", dat) 
(2, 30, 4886138, 229297, b'Down by the river') 
6

私はあるべき2つの新しい関数を作りました標準パックおよびアンパック機能のドロップイン置換として使用できます。両方とも、ASCIIZ文字列をパック/アンパックするための 'z'文字をサポートしています。フォーマット文字列中に「Z」の文字の出現場所や数に制限はありません。

import struct 

def unpack (format, buffer) : 
    while True : 
     pos = format.find ('z') 
     if pos < 0 : 
      break 
     asciiz_start = struct.calcsize (format[:pos]) 
     asciiz_len = buffer[asciiz_start:].find('\0') 
     format = '%s%dsx%s' % (format[:pos], asciiz_len, format[pos+1:]) 
    return struct.unpack (format, buffer) 

def pack (format, *args) : 
    new_format = '' 
    arg_number = 0 
    for c in format : 
     if c == 'z' : 
      new_format += '%ds' % (len(args[arg_number])+1) 
      arg_number += 1 
     else : 
      new_format += c 
      if c in 'cbB?hHiIlLqQfdspP' : 
       arg_number += 1 
    return struct.pack (new_format, *args) 

ここではそれらを使用する方法の例です:

>>> from struct_z import pack, unpack 
>>> line = pack ('<izizi', 1, 'Hello', 2, ' world!', 3) 
>>> print line.encode('hex') 
0100000048656c6c6f000200000020776f726c64210003000000 
>>> print unpack ('<izizi',line) 
(1, 'Hello', 2, ' world!', 3) 
>>> 
関連する問題