我正在使用几个二进制文件,我想解析存在的UTF-8字符串。
我目前有一个函数,它接受一个文件的起始位置,然后返回找到的字符串:
1
2
3
4
5
6def str_extract(file, start, size, delimiter = None, index = None):
file.seek(start)
if (delimiter != None and index != None):
return file.read(size).explode('0x00000000')[index] #incorrect
else:
return file.read(size)
文件中的一些字符串用0x00 00 00 00分隔,是否有可能像PHP的爆炸那样拆分它们? 我是Python的新手,所以欢迎任何有关代码改进的指针。
样本文件:
48 00 65 00 6C 00 6C 00 6F 00 20 00 57 00 6F 00 72 00 6C 00 64 00 | 00 00 00 00 | 31 00 32 00 33 00是Hello World123,我注意到00 00 00 00分隔符用|条围住它。
所以:
1str_extract(file, 0x00, 0x20, 0x00000000, 0) => 'Hello World'
同理:
1str_extract(file, 0x00, 0x20, 0x00000000, 1) => '123'
split是PHP explode的等效函数。 您是基于实际的0x00000000字符串进行拆分还是在检查文件中的实际零字节?
@figs检查4个零的实际序列。 我举了一个例子来说明我的观点。
那么文件中的|字符是什么?
它们实际上并不在我的文件中,只是用于指示一系列零的可读性管道。
这是在Python 2还是Python 3中?
Python 2,但我认为我不怕学习3解决方案。
我将假设你在这里使用Python 2,但是编写代码来处理Python 2和Python 3。
您有UTF-16数据,而不是UTF-8。您可以将其读作二进制数据,并使用str.split()方法拆分四个NUL字节:
1file.read(size).split(b'\x00' * 4)[index]
生成的数据被编码为UTF-16 little-endian(您可能在开始时省略或未省略UTF-16 BOM;您可以使用以下方法解码数据:
1result.decode('utf-16-le')
然而,这将失败,因为我们只是在最后一个NUL字节切断了文本; Python在找到的前4个NUL上进行拆分,并且不会跳过作为文本一部分的最后一个NUL字节。
更好的想法是首先解码为Unicode,然后拆分Unicode双NUL代码点:
1file.read(size).decode('utf-16-le').split(u'\x00' * 2)[index]
把它作为一个函数放在一起将是:
1
2
3
4
5
6
7
8
9
10def str_extract(file, start, size, delimiter = None, index = None):
file.seek(start)
if (delimiter is not None and index is not None):
delimiter = delimiter.decode('utf-16-le') # or pass in Unicode
return file.read(size).decode('utf-16-le').split(delimiter)[index]
else:
return file.read(size).decode('utf-16-le')
with open('filename', 'rb') as fobj:
result = str_extract(fobj, 0, 0x20, b'\x00' * 4, 0)
如果文件在开始时作为BOM,请考虑将文件打开为UTF-16而不是以:
1
2
3
4import io
with io.open('filename', 'r', encoding='utf16') as fobj:
# ....
并删除显式解码。
Python 2演示:
1
2
3
4
5
6
7>>> from io import BytesIO
>>> data = b'H\x00e\x00l\x00l\x00o\x00 \x00W\x00o\x00r\x00l\x00d\x00\x00\x00\x00\x001\x002\x003\x00'
>>> fobj = BytesIO(data)
>>> str_extract(fobj, 0, 0x20, '\x00' * 4, 0)
u'Hello World'
>>> str_extract(fobj, 0, 0x20, '\x00' * 4, 1)
u'123'
现在阅读BOM,我该如何检测到这一点?我更喜欢这种方式,因为它比显式解码更干净。
@VeraWang:你的文件将以字节FF FE开头(编码U + FEFF ZERO WIDTH NO-BREAK SPACE到UTF-16 little-endian)。
对不起,这里有另一个问题:这些文件包含英文,德文,法文,日文的字符,还可以包含字符串中没有的内容。 Python是否具有这些字符集的预定十六进制范围?如果有意义,我只想要"可读"字符。
@VeraWang:不,那没有多大意义。 :-)你的意思是你想过滤可打印的字符?标签,换行符,不间断空格等都带有意义,你必须更加具体。
是的,抱歉这是漫长的一天:-)。还包括空字节。
@VeraWang:你可以使用str.translate()来有效地删除某些代码点; result.translate({0: None})告诉方法将0代码点(NUL)映射到None,这意味着删除它。
@VeraWang:或者,让字典(其键必须是整数,表示Unicode代码点)映射到其他代码点(所以也是一个整数,但单个Unicode字符也可以),以用其他代码点替换代码点。所以result.translate({0: u?})会用问号替换NUL字符。
谢谢,我已经从这篇文章中学到了很多关于Python的知识!
首先,您需要以二进制模式打开文件。
然后你split str(或bytes,依赖于Python的版本),分隔符为四个零字节b'\0\0\0\0':
1
2
3
4
5
6def str_extract(file, start, size, delimiter = None, index = None):
file.seek(start)
if (delimiter is not None and index is not None):
return file.read(size).split(delimiter)[index]
else:
return file.read(size)
此外,您需要处理编码,因为str_extract只返回二进制数据,而您的测试数据是UTF-16小端,如Martijn Pieters所说:
1
2>>> str_extract(file, 0x00, 0x20, b'\0\0\0\0', 0).decode('utf-16-le')
u'Hello World'
此外:用is not None测试变量不是None。
不完全Hello World;更像是H\x00e\x00l\x00l\x00o\x00 \x00W\x00o\x00r\x00l\x00d,这是UTF-16小端数据。