用pyparsing解析字节字符串

时间:2020-08-22 22:39:27

标签: python python-3.x pyparsing

我有来自网络的数据包,我想到了使用pyparsing来检测消息并提取不同包类型中的特定数据。当然,来自网络的内容是字节字符串的形式,例如以下示例。

b'\x03\xff*******************************************************************'

其中*代表任何字符。请注意,没有使用诸如Unicode之类的特定编码。

我可以将pyparsing与字节字符串一起使用,当我明确指定要查找的内容时,它似乎可以正常工作,

expr = Suppress(b'\x03\xff')

现在我希望它找到一个20字节的序列,例如放在Suppress(b'\x03\xff')之后,它们可以是任何东西。如果可以使用DOTALL标志,我曾想过使用Regex('.{20}')这样的表达式,但是无论如何我都会得到错误: TypeError: cannot use a string pattern on a bytes-like object

那么我怎么能检测到这个20种字节的序列呢?

附录

尝试@FMc提案:

from pyparsing import *
expr = Suppress(b'\x03\xff') + Regex(b'.{20}')
line = b'\x03\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff--4353425352FGDSGSFDGBFSDBGfdeGRES'
print(expr.parseString(line, parseAll=False).dump())

给予

Traceback (most recent call last):
  File "<input>", line 2, in <module>
  File "lib\site-packages\pyparsing\core.py", line 2384, in __init__
    self.mayReturnEmpty = self.re_match("") is not None
TypeError: cannot use a bytes pattern on a string-like object

3 个答案:

答案 0 :(得分:2)

看来pyparsing仅适用于文本(str),而不适用于bytes

一种想法是使用latin-1(琐碎的编码)将字节转换为““ text”“”,然后使用以下内容解析为文本:

from pyparsing import *
expr = Suppress('\x03\xff') + Regex('.{20}')
line = b'\x03\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff--4353425352FGDSGSFDGBFSDBGfdeGRES'
for tok in expr.parseString(line.decode('latin-1'), parseAll=False):
    print(tok.encode('latin-1'))

输出:

$ python3 t.py 
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff--435342535'

答案 1 :(得分:1)

我不确定,但是我怀疑pyparsing是文本解析器,而不是字节解析器。我在文档中没有提到“二进制”或“字节”。 docs的第一段:

该文档提供了pyparsing库的使用说明, 一个易于使用的Python模块,用于构造和执行基本文本 解析器。

当我在pyparsing代码库的失败行之前添加print([expr])时,会打印很多内容,给我的印象是pyparsing将基于文本的假设烘焙到其代码库中。这是所有输出爆炸之前的摘要:

[<SP><TAB>]
[{{{~{","} ~{LineEnd}} W:(0123...)} [<SP><TAB>]}]
[{{{{~{","} ~{LineEnd}} W:(0123...)} [<SP><TAB>]}}...]
[{quotedString using single or double quotes | commaItem}]
[","]
[{Suppress:(",") [{quotedString using single or double quotes | commaItem}]}]
[b'\x03\xff']
Traceback (most recent call last):
  File "x.py", line 3, in <module>
    expr = Suppress(b'\x03\xff') + Regex(b'.{20}')
  File "/Users/.../lib/python3.7/site-packages/pyparsing.py", line 5100, in __init__
    super(TokenConverter, self).__init__(expr)  # , savelist)
  File "/Users/.../lib/python3.7/site-packages/pyparsing.py", line 4453, in __init__
    self.mayIndexError = expr.mayIndexError
AttributeError: 'bytes' object has no attribute 'mayIndexError'

最初,我有一个想法是使用编码方案(例如十六进制)来解析字节 与pyparsing。但是经过一些初步的实验,我意识到这条路似乎 复杂-可能行不通。 pyparsing库与 正则表达式,无论是在其API还是在其基础实现中。例如这个 无法按预期工作:

from pyparsing import Regex, ParseException

# Your input converted to hex.
line = b'\x03\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff--4353425352FGDSGSFDGBFSDBGfdeGRES'
hex_line = line.hex()

# An easy regex, naively converted to hex.
pattern = r'.{20}'
hex_pattern = pattern.encode().hex() # 2e7b32307d
rgx = Regex(hex_pattern)

# It doesn't work: the regex syntax has been lost. We end up searching for the
# literal 2e7b32307d, which isn't found.
try:
    print(rgx.parseString(hex_line, parseAll = False).dump())
except ParseException as e:
    print(e)

# A hex-minded regex for 20 arbitrary ACSII characters would be this:
hpattern = r'.{40}'
rgx = Regex(hpattern)

# This works.
print(rgx.parseString(hex_line, parseAll = False).dump())

但是那个例子太简单了。许多正则表达式概念不太方便 用十六进制表示:

\d+            # Regex
(?:3[0-9])+    # Hex-centric regex? No thanks.

除非您的需求很简单(如果是这样,为什么还要麻烦pyparsing), 路径似乎很难。再说一遍,不要忘记很多pyparsing的API 元素在引擎盖下作为常规正则表达式实现。

答案 2 :(得分:-1)

为了进行测试,我尝试了Anthony Sottile的想法,并在测试的行中使用了完整的字节频谱,但出现以下错误:

from pyparsing import *
expr = Suppress('\x03\xff') + Regex('.{20}')
line = b'\x03\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
for tok in expr.parseString(line.decode('latin-1'), parseAll=False):
    print(tok.encode('latin-1'))

Traceback (most recent call last):
  File "<input>", line 4, in <module>
  File "\lib\site-packages\pyparsing.py", line 1955, in parseString
    raise exc
  File "\lib\site-packages\pyparsing.py", line 3342, in parseImpl
    raise ParseException(instring, loc, self.errmsg, self)
pyparsing.ParseException: Expected Re:('.{20}'), found '\x00'  (at char 2), (line:1, col:3)

要使其正常运行,我需要以下内容:

from pyparsing import *
import re
expr = Suppress('\x03\xff') + Regex(re.compile(r'.{256}', re.DOTALL))
line = b'\x03\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
for tok in expr.parseString(line.decode('latin-1'), parseAll=False):
    print(tok.encode('latin-1'))
    print(len(tok), len(tok.encode('latin-1')))

输出:

b'\x00\x01\x02\x03\x04\x05\x06\x07\x08     \n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb'
256 256

但是打印的字符串以\xfb结尾,我想知道为什么为什么长度是256。而且看来TAB=\x09转换为5个空格,所以转换在\之前停了4个字符。 xff。 我发现parseStringTAB转换为空格。因此,通过添加parseWithTabs()效果很好:

from pyparsing import *                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
import re                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
expr = Suppress('\x03\xff') + Regex(re.compile(r'.{256}', re.DOTALL))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
line = b'\x03\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'    
expr.parseWithTabs()                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
for tok in expr.parseString(line.decode('latin-1'), parseAll=False):                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
    print(tok.encode('latin-1'))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    print(len(tok), len(tok.encode('latin-1')))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      

输出:

b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
256 256

附录

实际上,为确保在每种情况下都能正常工作,需要更新默认的空白字符,以使它们在某些情况下不会被跳过。在这种情况下,我选择使用setDefaultWhitespaceChars()

ParserElement.setDefaultWhitespaceChars("")
相关问题