多行正则表达式匹配

时间:2013-04-13 10:06:24

标签: python regex

我有一个看起来像这样的文件:

useless stuff

fruit: apple
fruit: banana

useless stuff

fruit: kiwi
fruit: orange
fruit: pear

useless stuff

这个想法是按照它们出现的顺序和按组捕获所有水果名称。在上面的例子中,输出必须是:

[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

我通过迭代多行regexp '^fruit: (.+)$'的所有匹配,并通过将水果名称添加到同一个给定列表中,如果看起来找到它们的行相互跟随,我成功了。

然而,这对于在水果名称上进行替换是不切实际的(跟踪匹配开始和结束索引然后成为强制性的),所以我宁愿在单个正则表达式中执行此操作。

我试过这个:

re.findall(r'(?:^fruit: (.+)$\n)+', thetext, re.M)

但它只返回一行。

我哪里错了?

6 个答案:

答案 0 :(得分:1)

这可以让你保留正则表达式,正如你所说,以后你可能需要更复杂的表达式:

>>> import re
>>> from itertools import groupby
>>> with open('test.txt') as fin:
        groups = groupby((re.match(r'(?:fruit: )(.+)', line) for line in fin),
                         key=bool) # groups based on whether each line matched
        print [[m.group(1) for m in g] for k, g in groups if k]
        # prints each matching group


[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

没有正则表达式:

>>> with open('test.txt') as f:
        print [[x.split()[1] for x in g]
               for k, g in groupby(f, key=lambda s: s.startswith('fruit'))
               if k]


[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

答案 1 :(得分:1)

我认为你会看到问题,如果你让内部组不像这样:

re.findall(r'(?:^fruit: (?:.+)$\n)+', thetext, re.M)
# result:
['fruit: apple\nfruit: banana\n', 'fruit: kiwi\nfruit: orange\nfruit: pear\n']

问题是每个匹配都匹配了一大堆fruit:行,但捕获组(在原始soln中)会多次捕获。由于捕获组只能有一个与之关联的值,因此最终会捕获最后一个捕获的子字符串(我认为最后一个选择是任意的;我不会指望这种行为)。

答案 2 :(得分:1)

另一种方式:

import re
with open('input') as file:
    lines = "".join(file.readlines())
    fruits = [[]]
    for fruit in re.findall(r'(?:fruit: ([^\n]*))|(?:\n\n)', lines, re.S):
        if fruit == '': 
            if len(fruits[-1]) > 0:
                fruits.append([])
        else:
            fruits[-1].append(fruit)
    del fruits[-1]
    print fruits

<强>输出

[['apple', 'banana'], ['kiwi', 'orange', 'pear']]

答案 3 :(得分:1)

你不能在正则表达式中以这种方式“分组”,因为通常一个组只捕获它的最新匹配。解决方法是按字面重复一组:

matches = re.findall(r'(?m)(?:^fruit: (.+)\n)(?:^fruit: (.+)\n)?(?:^fruit: (.+)\n)?', text)
# [('apple', 'banana', ''), ('kiwi', 'orange', 'pear')]

如果这适合您的任务(例如,不超过5-6组),您可以轻松地动态生成此类表达式。如果没有,唯一的选择是两次传球比赛(我猜这与你已有的相似):

matches = [re.findall(': (.+)', x) 
    for x in re.findall(r'(?m)((?:^fruit: .+\n)+)', text)]
# [['apple', 'banana'], ['kiwi', 'orange', 'pear']]

非标准(尚)regex模块提供了一种名为“捕获”的有趣方法。 m.captures(n)会返回群组的所有匹配项,而不仅仅是最新的匹配项,例如m.group(n)

import regex
matches = [x.captures(2) for x in regex.finditer(r'(?m)((?:^fruit: (.+)\n)+)', text)]
# [['apple', 'banana'], ['kiwi', 'orange', 'pear']]

答案 4 :(得分:0)

除非你绝对必须,否则我不喜欢使用正则表达式。向后退一步,看看你的情况,我的第一个倾向是想一想你是否应该在输入文件之前使用像awk这样的专用工具将输入文件按照csv进行按摩,然后再将其输入到python中。

话虽如此,你当然可以使用清晰的无正则表达式python完成你想做的事情。一个例子(我肯定可以在不牺牲透明度的情况下减少):

# newlst keeps track of whether you should start a new sublist
newlst=False
# result is the end result list of lists
result = []
# lst is the sublist which gets reset every time a grouping concludes
lst = []

with open('input.txt') as f:
    for line in f.readlines():
        # is the first token NOT a fruit?
        if line.split(':')[0] != 'fruit':
            # if so, start a new sublist
            newlst=True
            # just so we don't append needless empty sublists
            if len(lst) > 0: result.append(lst)
            # initialise a new sublist, since last line wasn't a fruit and
            # this implies a new group is starting
            lst = []
        else:
            # first token IS a fruit. So append it to the sublist
            lst.append(line.split()[1])

print result

答案 5 :(得分:0)

怎么样:

re.findall(r'fruit: ([\w]+)\n|[^\n]*\n', str, re.M);

结果:

['', '', 'apple', 'banana', '', '', '', 'kiwi', 'orange', 'pear', '']

这可以很容易地转换为[['apple','banana'],['kiwi','orange','pear']]

example in ideone