【问题标题】:Python: consecutive lines between matches similar to awkPython:匹配之间的连续行类似于awk
【发布时间】:2017-07-11 17:21:47
【问题描述】:

给定:

  • 多行字符串string(已从文件file读取)
  • 两个模式pattern1pattern2 将匹配每个string 中恰好一行的子字符串。这些线将被称为 line1 和 line2。

这些模式是正则表达式模式,但如果这样更容易,我可以更改它们的格式。

已搜索

我正在寻找一种方法来获取 python 中 line1 和 line2 之间的所有行(我们可以安全地假设 line1 在 line2 之前)。

当然,这可以在带有pattern1 设置的标志和pattern2 匹配时中断的for 循环中完成。不过,我正在这里寻找更compact 的解决方案。这是awk中的一个微不足道的oneliner:

awk '/pattern1/,/pattern2/' file

示例:

文件:

aaa aa a
bbb bb b
ccc cc c
ddd dd d
eee ee e
fff ff f

模式1:b bb

模式2:d dd

期望的结果:

bbb bb b
ccc cc c
ddd dd d

【问题讨论】:

标签: python regex string awk regex-lookarounds


【解决方案1】:

awk 中,/start/, /end/ 范围正则表达式打印找到/start/ 的整行,包括找到/end/ 模式的整行。它是一个有用的构造,已被 Perl、sed、Ruby 和其他人复制。

要在 Python 中执行范围运算符,请编写一个类来跟踪上一次调用 start 运算符直到 end 运算符的状态。我们可以使用正则表达式(如awk 所做的那样),或者可以将其修改为任何返回TrueFalse 状态的数据行。

鉴于您的示例文件,您可以这样做:

import re

class FlipFlop: 
    ''' Class to imitate the bahavior of /start/, /end/ flip flop in awk '''
    def __init__(self, start_pattern, end_pattern):
        self.patterns = start_pattern, end_pattern
        self.state = False
    def __call__(self, st):
        ms=[e.search(st) for e in self.patterns]
        if all(m for m in ms):
            self.state = False
            return True
        rtr=True if self.state else False
        if ms[self.state]:
            self.state = not self.state
        return self.state or rtr

with open('/tmp/file') as f:
    ff=FlipFlop(re.compile('b bb'), re.compile('d dd'))
    print ''.join(line if ff(line) else "" for line in f)

打印:

bbb bb b
ccc cc c
ddd dd d

这保留了逐行读取的文件,具有在其他语言中看到的/start/,/end/ 正则表达式的灵活性。当然,你可以对多行​​字符串做同样的方法(假设命名为s):

''.join(line+"\n" if ff(line) else "" for line in s.splitlines())

习惯上,在 awk 中,您可以使用标志获得与触发器相同的结果:

$ awk '/b bb/{flag=1} flag{print $0} /d dd/{flag=0}' file

你也可以在 Python 中复制它(用更多的词):

flag=False    
with open('file') as f:
    for line in f:
        if re.search(r'b bb', line):
            flag=True
        if flag:
            print(line.rstrip())
        if re.search(r'd dd', line):
            flag=False  

也可以与内存字符串一起使用。

或者,您可以使用多行正则表达式:

with open('/tmp/file') as f:
    print ''.join(re.findall(r'^.*b bb[\s\S]*d dd.*$', f.read(), re.M))

Demo and explanation

但这需要将整个文件读入内存。由于您声明字符串 已读入内存,因此在这种情况下这可能是最简单的:

''.join(re.findall(r'^.*b bb[\s\S]*d dd.*$', s, re.M))

【讨论】:

    【解决方案2】:

    使用 re.DOTALL 匹配任何内容,包括换行符。然后插入开始模式和结束模式:

    re.search( '[\w ]*b bb.*?d dd[ \w]*', string, re.DOTALL).group(0)
    

    注意:(1) string 这里是您要搜索的文件或字符串。 (2) 您需要import re。如果你真的想要简洁,也许会出错,你可以结合读取文件和提取模式:

    re.search( '[\w ]*b bb.*?d dd[ \w]*', open('file').read(), re.DOTALL).group(0) 
    

    【讨论】:

    • 不捕获pattern1 之前的行或pattern2 之后的行的剩余部分
    • 啊,OP改变了他想要的。编辑以适应。谢谢。
    • still 不包括带有b bb 的行的开头或带有d dd 的行的结尾。需要从头到尾打印整行。
    • 现在它只匹配第 2-n-1 行。而不是 \n 使用 re.M 然后将 ^$ 添加到您的模式中。
    • @dawg,我认为正确匹配数据,不是吗?而且,使用 re.M 意味着我必须在模式中包含换行符。
    【解决方案3】:

    使用regex

    >>> print(a)
    
    aaa aa a
    bbb bb b
    ccc cc c
    ddd dd d
    eee ee e
    fff ff f
    

    预期结果:

    >>> print(re.search('^.*bb b$\n((:?.+\n)+)^.*dd d$',a, re.M).group())
    bbb bb b
    ccc cc c
    ddd dd d
    

    或者只是附上的文字:

    >>> print(re.search('^.*bb b$\n((:?.+\n)+)^.*dd d$',a, re.M).group(1))
    ccc cc c
    

    【讨论】:

    • 正则表达式 ^.+bb b$\n((:?.+\n)+)^.+dd d$ 至少需要在 bb b 之前一个字符,在 dd d 之前至少一个字符,因为 .+.* 相比
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-13
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 2013-12-16
    相关资源
    最近更新 更多