正则表达式匹配以 `- [` 开头的最后一行

Posted

技术标签:

【中文标题】正则表达式匹配以 `- [` 开头的最后一行【英文标题】:Regex match up to last line that starts with `- [` 【发布时间】:2018-12-15 10:00:58 【问题描述】:

我有一个指定的正文块,其中包含一个 GitHub Markdown 列表,格式如下:

**HEADERONE**
- [x] Logged In
- [ ] Logged Out
- [x] Spun Around
- [x] Did the hokey pokey

但该列表被其他类似这样的垃圾包围:

A body paragraph about other things. Lorem ipsom and all that

**HEADERONE**
- [x] Logged In
- [ ] Logged Out
- [x] Spun Around
- [x] Did the hokey pokey

Maybe a link here www.go_ogle.com 

Another list that isn't important
- [ ] Thing one
- [ ] Thing two
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo  

我可以在抓取后以编程方式截断字符串,但我很好奇是否有一种非常干净的方式来抓取我的列表?标题总是相同的,所以从**HEADERONE** 抓取直到双新行的第一个实例可以正常工作。不过,从**HEADERONE** 抓取到最后一行末尾的- [ 会很棒。

我正在使用

\*\*HEADERONE\*\*[^*]*?(?=\n2)

虽然这在 regex101 中有效,但 re.search("\*\*HEADERONE\*\*[^*]*?(?=\n2)",body) 出于某种原因不返回任何内容。 所以我把它改成

\*\*HEADERONE\*\*[\S\s]*?(?=\n2)

但这太多了,包括第二个列表。有什么想法吗?

【问题讨论】:

有 CRLF 结尾吗?尝试将(?=\n2) 替换为(?=(?:\r\n)2)。替代方案可以是(?m)^\*\*HEADERONE\*\*(?:\r?\n-\s*\[[^][]*].*)* 试试(?m)\*\*HEADERONE\*\*(?:\s+^- +\[.*)+。在此处查看现场演示regex101.com/r/A8gahu/1 @WiktorStribiżew 用(?=(?:\r\n)2) 替换(?=\n2) 效果很好!虽然它停止在 regex101 上工作,但它在 python3 中运行良好。如果您可以回答解释第二个字符串的不同之处或原因,我将标记为已接受! 我还添加了一种非正则表达式方法来获取文本中的所有匹配项。 【参考方案1】:

您可以找到\*\*HEADERONE\*\* 和第一个空白行之间的所有内容,其中包含以下内容:

^(\*\*HEADERONE\*\*[\s\S]*?)^\s*$

Demo

[\s\S]*? 匹配所有字符,包括换行符,直到第一个空行。如果有可能没有空行或字符串结尾,您可以轻松地将该测试添加到表单中:

^(\*\*HEADERONE\*\*[\s\S]*?)(?:^\s*$|\Z)

Demo


如果您想使用 Python 非正则表达式的方式来获取该块,并且这些块由两个或多个新行分隔,您可以这样做:

print('\n'.join(block for block in s.replace('\r\n', '\n').split('\n\n') if block.lstrip().startswith('**HEADERONE**')))

Try it online

或者,如果你有一个文件:

print('\n'.join(block for block in fo.read() if block.lstrip().startswith('**HEADERONE**')))

其中fo是在文件模式下用'U'打开的文件。

【讨论】:

【参考方案2】:

虽然用(?=(?:\r\n)2) 替换(?=\n2) 可以解决问题,因为输入中有CRLF 结尾,但我建议使用更精确的模式:

m = re.search(r'^\*\*HEADERONE\*\*(?:\r?\n-\s*\[[^][]*].*)*', s, re.M)
if m:
    print(m.group())

查看regex demo 和Python demo。

说明

^ - 行首(re.M 重新定义了 ^ 锚行为) \*\*HEADERONE\*\* - **HEADERONE** 字符串 (?:\r?\n-\s*\[[^][]*].*)* - 零次或多次连续重复 \r?\n - 仅以 CRLF 或 LF 结尾 - - 一个连字符 \s* - 0+ 个空格 \[ - 一个 [ 字符 [^][]* - 除了 ][ 之外的 0+ 个字符 ] - 一个] .* - 该行的其余部分。

此外,还有一种方法可以使用非正则表达式方法来获取文件中的所有匹配项:

res = []
tmp = []
inblock = False
for line in f:  # f is a handle to the open file, or use s.splitlines() to split the string s into lines
    if line == '**HEADERONE**':
        tmp.append(line.rstrip())
        inblock = not inblock
    elif inblock and line.startswith("- ["):
        tmp.append(line.rstrip())
    else:
        if len(tmp) > 0:
            res.append("\n".join(tmp))
            tmp = []
            inblock = not inblock

请参阅Python demo online。基本上,一旦找到**HEADERONE**,所有以- [ 开头的后续行都附加到tmp,然后加入res 列表中的一个项目。

【讨论】:

很好的答案。考虑使用s.splitlines()s.split('\n')s.split('\n')\r\n 行尾混淆,s.splitlines() 普遍适用。如果您在这种情况下不关心尾随空格,您可能可以松开 .rstrip() @dawg 我明白了,记住这是一件好事,但是,s.split('\n') 实际上应该是打开文件的句柄,我更改了代码 sn-p。好吧,如果那是一个字符串而不是一个文件,那就更好了。 是的——我同意你对文件的看法。 \r\n vs \n 然后可以通过在文件打开模式中添加U 来处理Universal Line Support。【参考方案3】:
regex = r'\*\*HEADERONE\*\*(?:\n.+)+'
#^^^ HEADER followed by ONE newline and some other stuff
results = re.findall(regex, text)
print(results[0])
#**HEADERONE**
#- [x] Logged In
#- [ ] Logged Out
#- [x] Spun Around
#- [x] Did the hokey pokey

【讨论】:

以上是关于正则表达式匹配以 `- [` 开头的最后一行的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式以啥结尾

匹配 AWK 中多行的正则表达式。 && 操作员?

求一个匹配 以指定字符开头,指定字符结尾,中间内容任意的正则表达式

求一个匹配 以指定字符开头,指定字符结尾,中间内容任意的正则表达式

求一个匹配 以指定字符开头,指定字符结尾,中间内容任意的正则表达式

正则表达式中以啥开头啥结尾怎么写