A struct.calcsize() entry is pretty easy to handle, as struct.calcsize() will tell you the length it expects. You can use this and the actual data length to build a new format string for unpack() , which includes the correct string length.
This function is just a wrapper for unpack() , allowing the new format character at the last position to discard the NUL terminal:
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 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')
source share