我有很多日志文件,并且想使用多行搜索一些模式,但为了轻松找到匹配的字符串,我仍然想查看匹配区域的行号。
有什么好的建议。 (代码示例已复制)
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
这可以通过以下方式相当有效地完成:
{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
创建列表,但这意味着我们不知道何时停止存储换行符 (即使唯一的模式匹配位于,它最终也可能会存储许多换行符)文件的开头).
如果避免存储所有匹配项很重要 - 可以创建一个根据需要扫描换行符的迭代器,尽管不确定这在实践中会给您带来多大优势。
你想要的是正则表达式不太擅长的典型任务;解析。
您可以逐行读取日志文件,并在该行中搜索用于分隔搜索的字符串。您可以逐行使用正则表达式,但它比常规字符串匹配效率较低,除非您正在寻找复杂的模式。
如果您正在寻找复杂的匹配,我希望看到它。在不使用正则表达式的情况下,在文件中的每一行中搜索 ####
同时保持行数会更容易。
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))
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
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))
lineno: 1 matched: ttteest
lineno: 2 matched: ttest