使用python在大文件中寻找正则表达式

时间:2012-09-26 21:36:42

标签: python regex perl seek

我试图在一个文件中寻找一个标记':path,'然后将所有以下(任意数字计数)数字作为一个数字读取(因此对于':path,123'我寻找,在文件中然后读取整数123)。然后读取当前搜索位置和pos + 123之间的字符(将它们存储在列表或其他内容中)。然后寻找':path'的下一场比赛,然后重复这个过程。

我想要一个功能有点像:

def fregseek(FILE, current_seek, /regex/):

.
.
  value_found = ?  # result of reading next N chars after :path,[0-9]+
.
.
  return next_start_seek, value_found

一行中':path'可能有任意数量的匹配,并且该字符串可能出现在','之后指定的字符数内。我写了一堆乱七八糟的垃圾,每行写入,然后对于匹配所指示的前N个字符的每一行选择,然后继续处理字符串,直到它全部被吃掉。然后读取下一个字符串,依此类推。

这太可怕了,我不想从一个潜在的巨大文件中剔除所有线路,而我真正需要做的就是搜索(特别是因为换行是无关紧要的,因此需要额外的处理步骤只是因为行容易从文件中提取是荒谬的。)

所以,就是这样,我想解决的是我的问题。我需要寻找匹配,读取值,从该值的末尾继续寻找下一个匹配,依此类推,直到文件耗尽。

如果有人可以帮助我,我将很高兴收到他们的来信:)

我想尽可能避免使用非标准库,我也想要最短的代码,但这是我最不关心的问题(速度和内存消耗是重要的因素,但我不希望50 loc额外的用一个小函数来引导一些库,只要我知道它是什么就可以撕掉它。

我更喜欢python代码,但是,如果perl在这方面打败了python,我将使用perl,我也会对聪明的sed / awk / bash脚本等开放,只要它们的速度不是很慢。

非常感谢。

2 个答案:

答案 0 :(得分:3)

如果您不需要正则表达式,只需查找和切片即可完成此操作。

无论哪种方式,简单的解决方案是将整个文件读入内存,并查找并切片生成的str / bytes对象。

但如果您不能(或不想)将整个文件读入内存,那么这不起作用。

幸运的是,如果您可以指望您的文件是<< 2GB或者你只需​​要在64位Python中工作,并且你在一个合理的平台(POSIX,现代Windows等)上,你可以mmap将文件放入内存中。 mmap对象具有字符串所具有的相同方法的子集,因此您可以假装您有一个字符串,就像您将整个文件读入内存一样,但您可以依赖Python实现和操作系统让它以合理的效率工作。

根据您的Python版本,re可能无法扫描mmap,就像它是一个字符串一样,它可能会工作但速度很慢,或者它可能正常工作。所以,你最好先尝试一下,如果没有抛出异常或者比预期慢得多,你就完成了:

def findpaths(fname):
  with open(fname, 'rb') as f:
    m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    for match in re.finditer(':path,([0-9]+)', m):
      yield m[match.end():match.end()+int(match.group(1))]

(这与BrtH的答案相同,只是使用mmap而不是字符串,并重新构建为生成器而不是列表 - 尽管你当然可以通过用括号替换方括号来完成后一部分。)

如果你使用的是旧的(或非CPython?)版本的Python,它不能(有效地)re mmap,那就有点复杂了:

def nextdigits(s, start):
  return ''.join(itertools.takewhile(str.isdigit,
                                     itertools.islice(s, start, None)))

def findpaths(fname):
  with open(fname, 'rb') as f:
    m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    i = 0
    while True:
      n = m.find(':path', i)
      if n == -1: return
      countstr = nextdigits(m, n+6)
      count = int(countstr)
      n += 6 + len(countstr)
      yield m[n:n+count]
      i = n + 6 + count

这可能不是编写nextdigits函数的最快方法。我不确定这实际上是重要的(时间和时间),但如果确实如此,其他可能性是切出m[n+6:n+A_BIG_ENOUGH_NUMBER]和正则表达式,或编写自定义循环,或者...另一方面,如果这是你的瓶颈,通过使用JIT(PyPy,Jython或IronPython)切换到解释器可能会获得更多好处......

对于我的测试,我将事情分开:findpaths采用类似字符串的对象,调用者执行with openmmap位并将m传递给findpaths;我这里不是为了简洁而做到的。

无论如何,我已根据以下数据测试了两个版本:

BLAH:path,3abcBLAH:path,10abcdefghijklmnBLAH:path,3abc:path,0:path,3abc

输出结果为:

abc
abcdefghij
abc

abc

我认为这是对的吗?

如果我的早期版本导致它在100%CPU下旋转,我的猜测是我没有在循环中正确增加i;这是在紧密的解析循环中获得该行为的最常见原因。无论如何,如果您可以使用当前版本重现,请发布数据。

答案 1 :(得分:2)

你可以在python的近一行中完成:

with open('filename.txt') as f:
    text = f.read()

results = [text[i[0]:i[0] + i[1]] for i in 
           ((m.end(), int(m.group(1))) for m in
            re.finditer(':path,([0-9]+)', text))]

注意:未经测试......