【问题标题】:Regex - matching all text between two strings正则表达式 - 匹配两个字符串之间的所有文本
【发布时间】:2012-09-17 01:17:55
【问题描述】:

我目前正在解析具有以下结构的日志文件:

1) 时间戳,前面是#字符,后面是\n

2) 在该时间戳之后发生的任意 # 个事件,所有事件都跟在 \n

3) 重复..

这是一个例子:

#100
04!
03!
02!
#1299
0L
0K
0J
0E
#1335
06!
0X#
0[#
b1010 Z$
b1x [$
...

请原谅看似神秘的值,它们是代表某些“事件”的编码。

注意:事件编码也可以使用 # 字符。

我要做的是计算在特定时间发生的事件的数量。

换句话说,在时间 100,发生了 3 个事件。

我正在尝试匹配两个时间戳之间的所有文本 - 并通过简单地计算匹配文本中包含的换行符数来计算事件数。

我正在使用 Python 的正则表达式引擎,并且我正在使用以下表达式:

pattern = re.compile('(#[0-9]{2,}.*)(?!#[0-9]+)')

注意:{2,} 是因为我想要至少有两位数字的时间戳。

我匹配一个时间戳,继续匹配任何其他字符,直到遇到另一个时间戳 - 结束匹配。

返回的是:

#100
#1299
#1335

所以,我得到了时间戳——但没有任何事件数据——我真正关心的!

我认为造成这种情况的原因是负面的后视是“贪婪的”——但我并不完全确定。

可能有一个完全不同的正则表达式可以让这变得更简单 - 接受任何建议!

非常感谢任何帮助!

-k

【问题讨论】:

    标签: python regex regex-negation


    【解决方案1】:

    我认为正则表达式不是这里工作的好工具。你可以只使用一个循环..

    >>> import collections
    >>> d = collections.defaultdict(list)
    >>> with open('/tmp/spam.txt') as f:
    ...   t = 'initial'
    ...   for line in f:
    ...     if line.startswith('#'):
    ...       t = line.strip()
    ...     else:
    ...       d[t].append(line.strip())
    ... 
    >>> for k,v in d.iteritems():
    ...   print k, len(v)
    ... 
    #1299 4
    #100 3
    #1335 6
    

    【讨论】:

    • 您的解释器使用 2 个空格来缩进...?
    • 在 python 中,你可以使用 1 个空格,3 个空格,任何你喜欢的......只要你是一致的。我个人喜欢2个空间..
    • 嗯,直到。我以为解释器强制执行PEP8
    • 虽然这些都是不错的选择,但我还是很好奇原来的正则表达式有什么问题。我与@BrenBarn 在同一页面上,我需要包含 DOTALL 标志,但即使在那之后仍然存在问题。我将使用结果的方式 - 我认为如果我能够让它工作,正则表达式将更容易和最方便。有什么见解吗?
    • 正则表达式在使用得当时非常方便和强大。不过,使用代码 sn-p,这个问题肯定会更清晰、更干净。想象一下有人试图阅读/修改您的代码,并且不得不在心理上破译正则表达式在做什么.. 不好玩也不pythonic!
    【解决方案2】:

    如果您坚持使用基于正则表达式的解决方案,我建议:

    >>> pat = re.compile(r'(^#[0-9]{2,})\s*\n((?:[^#].*\n)*)', re.MULTILINE)
    >>> for t, e in pat.findall(s):
    ...     print t, e.count('\n')
    ...
    #100 3
    #1299 4
    #1335 6
    

    解释:

    (              
      ^            anchor to start of line in multiline mode
      #[0-9]{2,}   line starting with # followed by numbers
    )
    \s*            skip whitespace just in case (eg. Windows line separator)
    \n             new line
    (
      (?:          repeat non-capturing group inside capturing group to capture 
                   all repetitions
        [^#].*\n   line not starting with #
      )*
    )
    

    您似乎误解了负前瞻的作用。当它遵循.* 时,正则表达式引擎首先尝试使用尽可能多的字符,然后才检查前瞻模式。如果前瞻不匹配,它将逐个字符回溯,直到匹配为止。

    但是,您可以将 positive 前瞻与非贪婪的.*? 一起使用。此处.*? 将使用字符,直到前瞻在行首看到 # 或整个字符串的结尾:

    re.compile(r'(^#[0-9]{2,})\s*\n(.*?)(?=^#|\Z)', re.DOTALL | re.MULTILINE)
    

    【讨论】:

    • 您能否进一步解释一下为什么这个版本比其他版本更有效?谢谢!
    • @kbarber 添加了一些解释。
    【解决方案3】:

    原因是点不匹配换行符,所以你的表达式只会匹配包含时间戳的行;比赛不会跨越多条线。您可以将"dotall" flag 传递给re.compile,以便您的表达式将匹配多行。由于您说“事件编码”可能还包含 # 字符,因此您可能还想使用多行标志并在开头将匹配锚定为 ^,因此它仅匹配开头的 #行。

    【讨论】:

    • 这是您的想法吗? pattern = re.compile('^(#[0-9]{2,}.*)(?!#[0-9]+)', re.DOTALL | re.MULTILINE) -- 此版本匹配所有文本。如果我解释有误,请告诉我。
    • @kbarber 否定前瞻匹配除#[0-9]+ 之前的任何位置。它也在字符串的末尾匹配。因此贪婪的.* 能够匹配所有的文本。
    • 换句话说 - 你是说 .* 在负前瞻识别它之前吞噬下一个时间戳?
    【解决方案4】:

    您可以逐行遍历数据,并拥有一个仅存储与每个时间戳关联的事件数量的字典;不需要正则表达式。例如:

    with open('exampleData') as example:
        eventCountsDict = {}
        currEvent = None
        for line in example:
            if line[0] == '#': # replace this line with more specific timestamp details if event encodings can start with a '#'
                eventCountsDict[line] = 0
                currEvent = line
            else:
                eventCountsDict[currEvent] += 1
    
    print eventCountsDict
    

    该代码为您的示例数据打印{'#1299\n': 4, '#1335\n': 5, '#100\n': 3}(不包括...)。

    【讨论】:

    • 不错,但collections.defaultdict(int) 甚至collections.Counter 在这里是更优雅的选择
    • @wim ah collectionsitertools - 在我得到一些代码工作之前,我总是忘记使用你...
    • 谢谢,这是一个不错的选择。我忘了提到该文件在开头也有标题数据 - 这不会解释。在找到有效时间戳之前不开始添加到字典的最优雅的解决方案是什么?你会用国旗吗? - 这是首先想到的,但可能有更清洁的方法。
    • @kbarber 我会忽略行,直到我在遍历行时遇到第一个有效时间戳。不确定是否有更优雅的方式(除非标头每次都是相同的字节数)。
    猜你喜欢
    • 2011-08-31
    • 1970-01-01
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    相关资源
    最近更新 更多