彻底解析所有匹配项的文件

Exhaustively parse file for all matches

我有一个使用 pyparsing 解析一些日志文件的语法,但 运行 遇到了一个问题,即只返回第一个匹配项。有没有办法确保我得到详尽的匹配项?这是一些代码:

from pyparsing import Literal, Optional, oneOf, OneOrMore, ParserElement, Regex, restOfLine, Suppress, ZeroOrMore

ParserElement.setDefaultWhitespaceChars(' ')
dt = Regex(r'''\d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 20\d\d \d\d:\d\d:\d\d\,\d{3}''')
# TODO maybe add a parse action to make a datetime object out of the dt capture group
log_level = Suppress('[') + oneOf("INFO DEBUG ERROR WARN TRACE") + Suppress(']')
package_name = Regex(r'''(com|org|net)\.(\w+\.)+\w+''')
junk_data = Optional(Regex('\(.*?\)'))
guid = Regex('[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}')

first_log_line = dt.setResultsName('datetime') +                    \
            log_level('log_level') +                                \
            guid('guid') +                                          \
            junk_data('junk') +                                     \
            package_name('package_name') +                          \
            Suppress(':') +                                         \
            restOfLine('message') +                                 \
            Suppress('\n')
additional_log_lines = Suppress('\t') + package_name + restOfLine
log_entry = (first_log_line + Optional(ZeroOrMore(additional_log_lines)))
log_batch = OneOrMore(log_entry)

在我看来,最后两行相当于

log_entry := first_log_line | first_log_line additional_log_lines
additional_log_lines := additional_log_line | additional_log_line additional_log_lines
log_batch := log_entry | log_entry log_batch

或者类似的东西。我在想这个错误吗?当我执行 print(log_batch.parseString(data).dump()) 时,我只看到一个与所有预期标记的匹配项。

因此,有一种解决方法似乎可以解决问题。无论出于何种原因,scanString 确实 适当地遍历了它们,因此我可以非常简单地在生成器中获取我的匹配项:

matches = (m for m, _, _ in log_batch.scanString(data))

仍然不确定为什么 parseString 没有 详尽地工作,并且仍然有点担心我误解了有关 pyparsing 的一些东西,所以更多的指针是欢迎到这里。

您的 scanString 行为是一个强有力的线索。假设我写了一个表达式来匹配一个或多个项目,并且错误地定义了我的表达式,以至于我的列表中的第二个项目不匹配。然后 OneOrMore(expr) 会失败,而 expr.scanString 会 "succeed",因为它会给我 更多 匹配,但仍然会忽略我可能拥有的匹配想要,但解析错误。

import pyparsing as pp

data = "AAA _AB BBB CCC"

expr = pp.Word(pp.alphas)
print(pp.OneOrMore(expr).parseString(data))

给出:

['AAA']

乍一看,OneOrMore 似乎失败了,而 scanString 显示了更多匹配项:

['AAA']
['AB']  <- really wanted '_AB' here
['BBB']
['CCC']

这是一个使用 scanString 的循环,它不打印匹配项,而是打印匹配项之间的间隙以及它们的开始位置:

# loop to find non-matching parts in data
last_end = 0
for t,s,e in expr.scanString(data):
    gap = data[last_end:s]
    print(s, ':', repr(gap))
    last_end = e

给予:

0 : ''
5 : ' _'  <-- AHA!!
8 : ' '
12 : ' '

这是另一种可视化方式。

# print markers where each match begins in input string
markers = [' ']*len(data)
for t,s,e in expr.scanString(data):
    markers[s] = '^'

print(data)
print(''.join(markers))

打印:

AAA _AB BBB CCC
^    ^  ^   ^  

您的代码会稍微复杂一些,因为您的数据跨越多行,但是使用 pyparsinglinelinenocol 方法,您可以做类似的事情。