6

struct.unpack()ASCII文字列で終わるデータレコードを分解するために使用しようとしています。

レコード(たまたまTomTom ov2レコード)の形式は次のとおりです(リトルエンディアンで保存)。

  • 1バイト
  • 合計レコードサイズの4バイト整数(このフィールドを含む)
  • 4バイト整数
  • 4バイト整数
  • 可変長文字列、nullで終了

unpack()文字列の長さは、渡す形式に含まれている必要があります。2番目のフィールドと残りのレコードの既知のサイズ(13バイト)を使用して、文字列の長さを取得できます。

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

次に、完全な解凍を続行しますが、文字列はnullで終了しているため、実際にunpack()それを実行したいと思います。また、独自のサイズを含まない構造体に遭遇した場合に備えておくと便利です。

どうすればそれを実現できますか?

4

2 に答える 2

5

サイズのないレコードは、実際には、struct.calcsize()予想される長さがわかるため、処理がかなり簡単です。それとデータの実際の長さを使用してunpack()、正しい文字列の長さを含む新しいフォーマット文字列を作成できます。

この関数は、の単なるラッパーでunpack()あり、ターミナルNULをドロップする最後の位置に新しいフォーマット文字を許可します。

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')
于 2012-08-07T17:19:25.833 に答える