python正则表达式,多行匹配,但仍想获取行号

问题描述 投票:0回答:6

我有很多日志文件,并且想使用多行搜索一些模式,但为了轻松找到匹配的字符串,我仍然想查看匹配区域的行号。

有什么好的建议。 (代码示例已复制)

string="""
####1
ttteest
####1
ttttteeeestt

####2

ttest
####2
"""

import re
pattern = '.*?####(.*?)####'
matches= re.compile(pattern, re.MULTILINE|re.DOTALL).findall(string)
for item in matches:
    print "lineno: ?", "matched: ", item

[更新] lineno 是实际行号

所以我想要的输出看起来像:

    lineno: 1, 1
    ttteest
    lineno: 6, 2
    ttttteeeestt
python regex parsing
6个回答
12
投票

这可以通过以下方式相当有效地完成:

  • 查找所有匹配项
  • 循环换行,存储:
    {offset: line_number}
    映射直到最后一个匹配。
  • 对于每场比赛:
    • 预先反向求出第一个换行符的偏移量。
    • 在地图上查找它的线路号。

这可以避免每次匹配都倒数到文件开头。

以下功能类似

re.finditer

def finditer_with_line_numbers(pattern, string, flags=0):
    """
    A version of ``re.finditer`` that returns ``(match, line_number)`` pairs.
    """
    import re

    matches = list(re.finditer(pattern, string, flags))
    if matches:
        end = matches[-1].start()
        # -1 so a failed `rfind` maps to the first line.
        newline_table = {-1: 0}
        for i, m in enumerate(re.finditer("\\n", string), 1):
            # Don't find newlines past our last match.
            offset = m.start()
            if offset > end:
                break
            newline_table[offset] = i

        # Failing to find the newline is OK, -1 maps to 0.
        for m in matches:
            newline_offset = string.rfind("\\n", 0, m.start())
            line_number = newline_table[newline_offset]
            yield (m, line_number)

如果你想要内容,可以将最后一个循环替换为:

        for m in matches:
            newline_offset = string.rfind("\\n", 0, m.start())
            newline_end = string.find('\n', m.end())  # '-1' gracefully uses the end.
            line = string[newline_offset + 1:newline_end]
            line_number = newline_table[newline_offset]
            yield (m, line_number, line)

请注意,最好避免从

finditer
创建列表,但这意味着我们不知道何时停止存储换行符 (即使唯一的模式匹配位于,它最终也可能会存储许多换行符)文件的开头).

如果避免存储所有匹配项很重要 - 可以创建一个根据需要扫描换行符的迭代器,尽管不确定这在实践中会给您带来多大优势。


8
投票

你想要的是正则表达式不太擅长的典型任务;解析。

您可以逐行读取日志文件,并在该行中搜索用于分隔搜索的字符串。您可以逐行使用正则表达式,但它比常规字符串匹配效率较低,除非您正在寻找复杂的模式。

如果您正在寻找复杂的匹配,我希望看到它。在不使用正则表达式的情况下,在文件中的每一行中搜索 ####

 同时保持行数会更容易。


7
投票
您可以仅预先存储行号,然后再查找。

import re string=""" ####1 ttteest ####1 ttttteeeestt ####2 ttest ####2 """ end='.*\n' line=[] for m in re.finditer(end, string): line.append(m.end()) pattern = '.*?####(.*?)####' match=re.compile(pattern, re.MULTILINE|re.DOTALL) for m in re.finditer(match, string): print 'lineno :%d, %s' %(next(i for i in range(len(line)) if line[i]>m.start(1)), m.group(1))
    

2
投票
finditer 函数可以告诉你匹配的字符范围。由此,您可以使用简单的换行符正则表达式来计算匹配之前有多少换行符。将换行数加一即可获取行号,因为我们在编辑器中操作文本的惯例是将第一行称为 1 而不是 0。

def multiline_re_with_linenumber(): string=""" ####1 ttteest ####1 ttttteeeestt ####2 ttest ####2 """ re_pattern = re.compile(r'.*?####(.*?)####', re.DOTALL) re_newline = re.compile(r'\n') count = 0 for m in re_pattern.finditer(string): count += 1 start_line = len(re_newline.findall(string, 0, m.start(1)))+1 end_line = len(re_newline.findall(string, 0, m.end(1)))+1 print ('"""{}"""\nstart={}, end={}, instance={}'.format(m.group(1), start_line, end_line, count))

给出这个输出

"""1 ttteest """ start=2, end=4, instance=1 """2 ttest """ start=7, end=10, instance=2
    

1
投票
我相信这或多或少符合你的要求:

import re string=""" ####1 ttteest ####1 ttttteeeestt ####2 ttest ####2 """ pattern = '.*?####(.*?)####' matches = re.compile(pattern, re.MULTILINE|re.DOTALL) for match in matches.finditer(string): start, end = string[0:match.start()].count("\n"), string[0:match.end()].count("\n") print("lineno: %d-%d matched: %s" % (start, end, match.group()))

它可能比其他选项慢一点,因为它重复执行子字符串匹配并在字符串上搜索,但由于您的示例中的字符串很小,我认为为了简单性而进行权衡是值得的。

我们在这里获得的也是与模式匹配的行的范围

,这使我们能够一次性提取整个字符串。我们可以通过计算比赛中换行符的数量来进一步优化这一点,而不是直接结束,因为它的价值。

import re text = """ ####1 ttteest ####1 ttttteeeestt ####2 ttest ####2 """ pat = ('^####(\d+)' '(?:[^\S\n]*\n)*' '\s*(.+?)\s*\n' '^####\\1(?=\D)') regx = re.compile(pat,re.MULTILINE) print '\n'.join("lineno: %s matched: %s" % t for t in regx.findall(text))

-2
投票
结果

lineno: 1 matched: ttteest lineno: 2 matched: ttest


© www.soinside.com 2019 - 2024. All rights reserved.