【问题标题】:How do I match the last occurrence of a complex repeating pattern如何匹配最后一次出现的复杂重复模式
【发布时间】:2020-01-30 08:42:08
【问题描述】:

注意
@Toto 接受的答案最终在现实世界中在具有许多可能匹配项的大文件上效果更好,但是,如果您在任何大小的文件上使用类似的正则表达式,只有几个可能的匹配项,那么 @Pavel Lint 的答案会很好,可能会更快,尽管我没有测试过速度。请记住,此处的差异以毫秒为单位,因此实际上差别不大,除非您连续多次执行此操作。


我有以下字符串,它来自 SQL 错误日志文件的一部分(为清楚起见进行了修改):

2020-01-27 11:12:00.72 备份日志已备份。数据库:ReportServerTempDB,创建日期(时间):2019/05/22(12:31:06),第一个LSN:79:1911:1,最后一个LSN:79:1933:1,转储设备数量:1,设备信息:(文件=1,类型=磁盘:{'E:\SQLLogDumps\ReportServerTempDB_tlog_20200127111200.trn'})。这只是一条信息性消息。无需用户操作。
2020-01-27 11:21:47.95 登录错误:17806,严重性:20,状态:14。
2020-01-27 11:21:47.95 登录次数一

2020-01-27 11:21:47.95 登录错误:18452,严重性:14,状态:1。
2020-01-27 11:21:47.95 登录 登录失败。登录来自不受信任的域,不能与集成身份验证一起使用。 [客户端:192.168.4.208]
2020-01-27 11:21:47.95 登录错误:17806,严重性:20,状态:14。
2020-01-27 11:21:47.95 登录次数二

2020-01-27 11:21:47.95 登录错误:18452,严重性:14,状态:1。
2020-01-27 11:21:47.95 登录 登录失败。登录来自不受信任的域,不能与集成身份验证一起使用。 [客户:192.168.4.208]

上面的粗体字是我目前所写的表达式所匹配的内容。
我想找到包含“严重性:20”的行加上以下行的 last 出现,也就是第二个粗体部分。然而,在野外,一个文档中可能出现任意数量的事件。

我在 regex101.com 上玩过,并在 regular-expressions.info 上阅读了很多内容,但无法制作有效的正则表达式。

我目前的表达是:
^.*Severity: 20.*\R^.*(?!(^.*\R)*Severity: 20)

我还在 Google 上进行了很多探索,但找不到任何有助于匹配跨越多行且属于长文档一部分的重复模式。

是否可以只匹配模式的最后一次出现?如果有,怎么做?

为了澄清,我正在寻找纯正则表达式。我知道这在 Python 中是可能的,方法是将所有匹配项作为列表返回,然后获取列表的最后一个元素,但我没有那个选项。另外,我正在使用 Notepad++ 正则表达式搜索,我相信它使用 Boost 版本的正则表达式。


更新

两个给定的答案都可以在测试字符串上完美运行,但是在大型/真实文件上的结果非常有趣,有关更多详细信息,请参见下文。

根据上面的测试字符串,我对@Pavel Lint 和@Toto 的答案进行了基准测试。 @Pavel Lint 的回答要快得多,大约是毫秒的 10 倍。

@Pavel Lint
^.+Severity: 20.*\R^.*\R(?![\s\S]*Severity: 20)
1588步,平均2ms

@Toto
[\s\S]+\K^.*?Severity: 20.*\R.*
61955步,平均36ms

但是,实际文件的结果有很大不同。现在,我无法提供我用于测试的实际文件,因为它是客户端的日志文件,所以我不会责怪@Pavel Lint 的答案,因为它在测试字符串上工作得非常好。我的问题可能不够清楚,关于这需要在野外做什么,但我确实声明“在野外,一个文档中可能有任何次出现”,我相信这涵盖了我们希望避免灾难性回溯的场景。

测试文件有 26,192 行,出现 1595 次“严重性:20”错误(与测试字符串中显示的模式匹配,但第二行中的消息会有所不同,可能包含任何单词/数字/特殊字符)。

上面的第一个正则表达式来自@Pavel Lint,当我运行它时杀死了 Notepad++。
我的半受过教育的猜测是发生了灾难性的回溯。

来自@Toto 的第二个正则表达式几乎立即与最后一次匹配。

最终,@Toto 的回答对于小样本来说速度较慢,但​​可以扩展到具有许多可能匹配项的非常大的文件。

【问题讨论】:

  • 在 notepad++ 上,您可以简单地将搜索方向更改为“向上”,然后从末尾开始搜索下一个结果。无论搜索结果如何,都将是最后一个匹配项。
  • 我刚刚看了看,选择“正则表达式”选项将搜索方向限制为向下。是的,我知道我可以寻找像“严重性:20”这样简单的东西而不使用正则表达式,但如果可能的话,我想使用正则表达式。
  • 你用的是什么版本?我在 v7.7.1 上,可以向后搜索就好了
  • 啊,我明白了,由于其他原因(使用“Python 脚本”插件),我使用的是 7.3.3。我想它是后来添加的。
  • 如果你不能升级,那么你就不走运了。正则表达式没有“第一个”或“最后一个”的概念。另一种方法是使用“在当前文档中查找全部”,然后单击出现的窗口的最后一行。或者您可以打开“. 匹配换行符”搜索.+<rest of the pattern>,匹配的结尾将是您要查找的行。

标签: regex notepad++


【解决方案1】:
  • Ctrl+F
  • 查找内容:[\s\S]+\K^.*?Severity: 20.*\R.*
  • 检查 环绕
  • CHECK 正则表达式
  • 取消选中 . matches newline
  • 查找下一个

说明:

[\s\S]+             # 1 or more any character
\K                  # forget all we have seen until this position
^                   # beginning of line
  .*?               # 0 or more any character but newline, not greedy
  Severity: 20      # literally
  .*                # 0 or more any character but newline
  \R                # any kind of linebreak
  .*                # 0 or more any character but newline

屏幕截图:

【讨论】:

  • 我可以按照这个,但我不明白如果没有负前瞻它是如何工作的..?我认为它与\K?
  • @Jlanger: \K 就像一个可变长度的后视。它之所以有效,是因为 [\s\S]+ 贪婪地匹配任何字符,直到包含 Severity: 20 \K 的最后一行从匹配中删除此行之前遇到的所有内容
  • 感谢您的解释,我现在无法测试它,但我猜这会比任何涉及负前瞻的方法更快?因为它不涉及回溯......我问的原因是一个 SQL 错误日志文件可能有 10k+ 行,其中包含数千个严重性 20 错误,在这种情况下,我相当确定回溯会杀死正则表达式(灾难性回溯? )
  • @Jlanger:我还没有做基准测试,但我认为它会更快。
  • 明天上班的时候测试一下。您的答案更简单,可能更快,但我想确认一下,以便我可以用任何进一步的相关信息(关于速度)更新问题,然后我会接受。如果您愿意,请随意对其进行基准测试,我的方法是查看每个人采取了多少步,如果您有更好的方法,请告诉我?
【解决方案2】:

我已经能够用这个正则表达式实现你想要的:

^.+Severity: 20.*\R^.*\R(?![\s\S]*Severity: 20)

这与您最初提出的想法几乎相同,但在否定前瞻块中,我将 .* 替换为 [\s\S]* 以便它适用于换行符。

这是演示:https://regex101.com/r/XWWFYc/1/

【讨论】:

  • 所以我已经对答案进行了基准测试,你的答案要快得多,即使它涉及到负前瞻,我预计会慢一些。你知道为什么会这样吗? (为了我/其他人了解原因)
  • 感谢您的回答,我认为这对于寻找此信息的其他人仍然非常有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-21
  • 2017-02-22
  • 1970-01-01
  • 2019-11-15
  • 2019-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多