轻微的正则表达式混淆-使用多行标志时的$行为

时间:2018-10-07 13:38:27

标签: python regex

请考虑以下文本:

SELECT
    CAST(POS.BALANCE AS DECIMAL(18, @currencyround)) AS DBAmount
FROM 
    dbo.POS_SALES POS  

您可能已经猜到了,我想从每个部分中提取! interesting1 a not interesting b interesting2 c ! interesting1 a not interesting b interesting2 c ! interesting1 a not interesting b interesting2 c not interesting arbitrary text d ! ac行是可选的,但是如果也有interesting2 c(按节),我只需要a

使用c我得到:

!\n(interesting1 (?P<a>.*?)$.*?(?:interesting2 (?P<c>.*?))?$\n(?=!))a来自最前面的两个部分,但(可以理解)来自最后一个部分的ca。请参见regex101

我怀疑这是在这种情况下最有效的正则表达式,因为这小段文字需要438个步骤,因此我愿意接受任何其他更有效的解决方案以获取正确的结果。

如果我将正则表达式更改为c\nnot interesting arbitrary text d(而不是捕获组!\n(interesting1 (?P<a>.*?)$.*?(?:interesting2 (?P<c>\w+))?$\n(?=!))中的\w+的{​​{1}}),则它在第三部分匹配的唯一内容是{{1} }(由于.*?不包含c,因此可以预期。)

我不了解如何使用a来指定
\w和结束\n之间的任意文本的可选行。

使用可选的非捕获组和$的不同变体形式不能给我正确的结果。我什至在前瞻部分中尝试了可选的非捕获组(以表示我们在!之前可能还有其他/可选的内容)。

2 个答案:

答案 0 :(得分:1)

我用这个

import re

text=\
"""
!
interesting1 a
not interesting b
interesting2 c
!
interesting1 a
not interesting b
interesting2 c
!
interesting1 a
not interesting b
interesting2 c
not interesting d
!
"""

pa = re.compile(r'^interesting[12] ([a-zA-Z]){1}', re.MULTILINE)
m = pa.findall(text)
print(m)

它有6个算数,128个步骤。

enter image description here

答案 1 :(得分:1)

  

我不了解如何使用$来指定interesting2 c和结束!之间的任意文本的可选行。

这是因为$与匹配可选的文本行无关。 $只是一个锚,它在字符串的末尾(如果正则表达式处于多行模式,则在换行符之前)声明一个位置。完全不需要匹配一行文本。

您的正则表达式无法正常工作的原因非常简单:缺少与可选行匹配的内容。正如我之前说过的,$只是一个锚点-它不会使用任何文本。因此,为了成功匹配您的(?=!)前瞻,组c必须增长并匹配所有文本,直到!字符为止。为了防止这种情况的发生,您必须添加可以匹配最后一行的内容,例如.*?[^\n]*

但是,在这种特定情况下,这并不像在.*?前行添加(?=!)那样简单。为什么?由于c组是可选的,因此在末尾添加.*?将阻止c组的匹配:

!\n(interesting1 (?P<a>.*?)$.*?(?:interesting2 (?P<c>\w+))?$\n.*?(?=!))
                            ^  ^                              ^
                            |  |                              this .*? would grow
                            |  |                              and consume the
                            |  |                              "interesting2 c"
                            |  this group is optional, so it would be skipped
                            this .*? would match the empty string

因此,最好是从头开始重写正则表达式。

这是我的写法:

!\ninteresting1 (?P<a>.*)(?:\n[^!].*)*\ninteresting2 (?P<c>.*)

逻辑很简单:

  1. !\ninteresting1 (?P<a>.*)匹配第一行并捕获a
  2. (?:\n[^!].*)*跳过所有以!开头的行
  3. \ninteresting2 (?P<c>.*)匹配并捕获c

这与您的正则表达式略有不同,因为仅当ac同时存在于一个节中时,它才会产生匹配项。另请参见online demo