17

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

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

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
4

6 回答 6

8

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

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

如果你正在寻找复杂的比赛,我想看看。####如果没有正则表达式,在保持行数的同时搜索文件中的每一行会更容易。

于 2013-05-21T15:41:16.690 回答
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))
于 2013-05-21T16:25:46.297 回答
5

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

  • 查找所有匹配项
  • 循环换行,将{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 not matches:
        return []

    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(r'\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,但这意味着我们不知道何时停止存储换行符(即使唯一的模式匹配位于文件开头,它也可能最终存储许多换行符) .

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

于 2017-07-17T11:02:07.130 回答
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
于 2017-06-10T22:33:14.367 回答
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()))

它可能比其他选项慢一点,因为它反复对字符串进行子字符串匹配和搜索,但由于在您的示例中字符串很小,我认为为了简单起见值得权衡。

我们在这里获得的也是与模式匹配的行范围,这使我们也可以一口气提取整个字符串。我们可以通过计算匹配中换行符的数量来进一步优化这一点,而不是直接到最后,因为它的价值。

于 2018-02-06T16:47:01.037 回答
-2
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))

结果

lineno: 1  matched: ttteest
lineno: 2  matched: ttest
于 2013-05-21T17:24:50.797 回答