Python:在文件中查找正则表达式

时间:2011-02-14 05:44:33

标签: python regex

有:

f = open(...)  
r = re.compile(...)

需要:
在大文件中找到第一个匹配正则表达式的位置(开始和结束)? (从current_pos=...开始)

我该怎么做?


我想拥有这个功能:

def find_first_regex_in_file(f, regexp, start_pos=0):  
   f.seek(start_pos)  

   .... (searching f for regexp starting from start_pos) HOW?  

   return [match_start, match_end]  

文件'f'预计会很大。

3 个答案:

答案 0 :(得分:31)

搜索大文件的一种方法是使用mmap库将文件映射到大内存块。然后你可以搜索它而不必明确地阅读它。

例如:

size = os.stat(fn).st_size
f = open(fn)
data = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)

m = re.search(r"867-?5309", data)

这适用于非常大的文件(我已经为30 GB以上的文件完成了它,但如果你的文件超过GB或者两个,你需要一个64位操作系统。)

答案 1 :(得分:4)

以下代码适用于大小约2GB的测试文件。

def search_file(pattern, filename, offset=0):
    with open(filename) as f:
        f.seek(offset)
        for line in f:
            m = pattern.search(line)
            if m:
                search_offset = f.tell() - len(line) - 1
                return search_offset + m.start(), search_offset + m.end()

请注意,正则表达式不得跨越多行。

答案 2 :(得分:0)

注意:这已在python2.7上测试过。你可能不得不在python 3中调整一些东西来处理字符串和字节,但它不应该太痛苦。

内存映射文件可能不适合您的情况(32位模式增加了连续虚拟内存不足的可能性,无法从管道或其他非文件读取等)。

这是一个一次读取128k块的解决方案,只要你的正则表达式匹配小于该大小的字符串,这就行了。另请注意,您不受使用单行正则表达式的限制。这个解决方案运行速度很快,但我怀疑它会比使用mmap慢一点。这可能更多地取决于您在比赛中所做的事情,以及您正在搜索的正则表达式的大小/复杂程度。

该方法将确保在内存中最多只保留2个块。在某些用例中,您可能希望在每个块中强制执行至少1个匹配作为完整性检查,但此方法将截断以便在内存中保留最多2个块。它还确保不会产生任何吃到当前块结尾的正则表达式匹配,而是保留最后一个位置,以便在真正的输入用尽或者我们有另一个正则表达式在结束之前匹配的块时,为了更好地匹配像" [^ \ n] +"或" xxx $"。如果你在xx(?!xyz)的正则表达式末尾有一个前瞻,你可能仍然能够破解,其中yz在下一个块中,但在大多数情况下,你可以使用这些模式。

import re

def regex_stream(regex,stream,block_size=128*1024):
    stream_read=stream.read
    finditer=regex.finditer
    block=stream_read(block_size)
    if not block:
        return
    lastpos = 0
    for mo in finditer(block):
        if mo.end()!=len(block):
            yield mo
            lastpos = mo.end()
        else:
            break
    while True:
        new_buffer = stream_read(block_size)
        if not new_buffer:
            break
        if lastpos:
            size_to_append=len(block)-lastpos
            if size_to_append > block_size:
                block='%s%s'%(block[-block_size:],new_buffer)
            else:
                block='%s%s'%(block[lastpos:],new_buffer)
        else:
            size_to_append=len(block)
            if size_to_append > block_size:
                block='%s%s'%(block[-block_size:],new_buffer)
            else:
                block='%s%s'%(block,new_buffer)
        lastpos = 0
        for mo in finditer(block):
            if mo.end()!=len(block):
                yield mo
                lastpos = mo.end()
            else:
                break
    if lastpos:
        block=block[lastpos:]
    for mo in finditer(block):
        yield mo

要测试/探索,您可以运行:

# NOTE: you can substitute a real file stream here for t_in but using this as a test
t_in=cStringIO.StringIO('testing this is a 1regexxx\nanother 2regexx\nmore 3regexes')
block_size=len('testing this is a regex')
re_pattern=re.compile(r'\dregex+',re.DOTALL)
for match_obj in regex_stream(re_pattern,t_in,block_size=block_size):
    print 'found regex in block of len %s/%s: "%s[[[%s]]]%s"'%(
        len(match_obj.string),
        block_size,match_obj.string[:match_obj.start()].encode('string_escape'),
        match_obj.group(),
        match_obj.string[match_obj.end():].encode('string_escape'))

这是输出:

found regex in block of len 46/23: "testing this is a [[[1regexxx]]]\nanother 2regexx\nmor"
found regex in block of len 46/23: "testing this is a 1regexxx\nanother [[[2regexx]]]\nmor"
found regex in block of len 14/23: "\nmore [[[3regex]]]es"

这可以与快速解析大型XML结合使用,在大型XML中可以基于作为root的子元素将其拆分为迷你DOM,而不必在使用SAX解析器时深入处理回调和状态。它还允许您更快地过滤XML。但我也将它用于其他许多用途。我在网上更容易获得这种令人惊讶的食谱!

还有一件事:只要传入的流生成unicode字符串,解析unicode就应该工作,如果你使用像\ w这样的字符类,你需要添加re。 U标志为re.compile模式构造。在这种情况下,block_size实际上意味着字符数而不是字节数。