2011-02-14 33 views
13

有:的Python:找到正則表達式的文件

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',預計要大。

+0

你能展示一個你想做什麼的更完整的例子嗎?隨着一些樣本輸入和輸出一起。 – 2011-02-14 05:50:23

回答

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的大小,但你需要一個64位操作系統,如果你的文件不止是GB或兩個)。

+0

看起來不錯,我會盡快檢查它 – Sergey 2011-02-14 06:13:02

3

以下代碼可以很好地處理大小約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() 

請注意,正則表達式不能跨越多行。

0

注意:這已經在python2.7上測試過。你可能需要調整python 3中的東西來處理字符串和字節,但是希望這不會太痛苦。內存映射文件可能不適合您的情況(32位模式增加沒有足夠的連續虛擬內存的機會,不能從管道或其他非文件等中讀取)。

下面是一個解決方案,一次讀取128k塊,只要您的正則表達式匹配比這個大小小的字符串,這將工作。另請注意,您不受限於使用單行正則表達式。這個解決方案運行得很快,但我懷疑它比使用mmap稍微慢一些。它可能更多地取決於你在比賽中所做的事情,以及你正在搜索的正則表達式的大小/複雜性。

該方法將確保在內存中只保留最多2個塊。在某些使用情況下,您可能希望強制每塊至少執行1次匹配作爲完整性檢查,但此方法會截斷以保留內存中最多2個塊。它還確保當前塊的結尾處的任何正則表達式匹配都不會生成,而是在真實輸入耗盡或正則表達式匹配之前匹配的另一個塊時保存最後一個位置,爲了更好地匹配「[^ \ n] +」或「xxx $」等模式。如果你看到正則表達式像xx(?!xyz)這樣的yx位於下一個塊中,但是在大多數情況下你可以使用這種模式來解決這個問題,你仍然可以打破它。

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,其中結合使用它可以根據一個子元素分解爲微型DOM,而不必在使用SAX解析器時深入處理回調和狀態。它還允許您更快地過濾XML。但是我也將它用於其他許多用途。我有點驚訝的食譜,這不是更容易在網上提供!

還有一件事:只要傳入的流正在生成unicode字符串,並且如果使用像\ w這樣的字符類,則需要將re.U標誌添加到re.compile模式構建。在這種情況下,block_size實際上是指字符數而不是字節數。