Perl正则表达式匹配较长句子中的可选短语

Sco*_*ott 4 regex perl

我试图在句子中匹配一个可选的(可能存在的)短语:

perl -e '$_="word1 word2 word3"; print "1:$1 2:$2 3:$3\n" if m/(word1).*(word2)?.*(word3)/'
Run Code Online (Sandbox Code Playgroud)

输出:

1:word1 2: 3:word3
Run Code Online (Sandbox Code Playgroud)

我知道第一个 '.*' 是贪婪的并将所有内容匹配到 'word3'。使它不贪婪无济于事:

perl -e '$_="word1 word2 word3"; print "1:$1 2:$2 3:$3\n" if m/(word1).*?(word2)?.*(word3)/'
Run Code Online (Sandbox Code Playgroud)

输出:

1:word1 2: 3:word3 
Run Code Online (Sandbox Code Playgroud)

这里似乎存在利益冲突。我原以为 Perl 会匹配 (word2)?如果可能,仍然满足非贪婪的 .*?。至少这是我对“?”的理解。Perl 正则表达式页面显示“?” 制作 1 次或零次,所以它不应该更喜欢一场比赛而不是零次吗?

更令人困惑的是,如果我捕获 .*?:

perl -e '$_="word1 word2 word3"; print "1:$1 2:$2 3:$3 4:$4\n" if m/(word1)(.*?)(word2)?.*(word3)/'
Run Code Online (Sandbox Code Playgroud)

输出:

1:word1 2: 3: 4:word3
Run Code Online (Sandbox Code Playgroud)

这里的所有组都是捕获组,所以我不知道为什么它们是空的。

只是为了确保没有捕获字间空间:

perl -e '$_="word1_word2_word3"; print "1:$1 2:$2 3:$3 4:$4\n" if m/(word1)(.*?)(word2)?.*(word3)/'
Run Code Online (Sandbox Code Playgroud)

输出:

1:word1 2: 3: 4:word3
Run Code Online (Sandbox Code Playgroud)

鉴于唯一未捕获的匹配项是 word2 和 word3 之间的匹配项,我只能假设它是进行匹配的匹配项。果然:

perl -e '$_="word1_word2_word3"; print "1:$1 2:$2 3:$3 4:$4 5:$5\n" if m/(word1)(.*?)(word2)?(.*)(word3)/'
Run Code Online (Sandbox Code Playgroud)

输出:

1:word1 2: 3: 4:_word2_ 5:word3
Run Code Online (Sandbox Code Playgroud)

所以贪婪匹配是向后工作的,Perl 很乐意匹配 word2 的零个(而不是一个)实例。让它不贪婪也无济于事。

所以我的问题是:我如何编写我的正则表达式来匹配和捕获句子中可能的短语?我这里给出的例子很简单;我解析的实际句子要长得多,我匹配的句子之间有很多词,所以我不能假设中间文本的长度或组成。

非常感谢,斯科特

Wik*_*żew 5

背景:懒惰和贪婪的量词是如何工作的

您需要了解贪婪和懒惰量词的工作原理。贪婪的人会立即抓取他们的模式可以匹配的文本,然后引擎将回溯,即它会尝试回到贪婪量化的子模式与子字符串匹配的地方,尝试检查下一个子模式是否可以匹配。

惰性匹配模式只是先匹配最少的字符,然后再尝试匹配其余的子模式。使用*?,它匹配零个字符,一个空格,然后继续检查下一个模式是否可以匹配,只有当不能匹配时,惰性子模式才会“扩展”以包含更多字符,依此类推。

所以,(word1).*(word2)?.*(word3)将匹配word2第一个.*(第二个.*将匹配一个空格,因为第一个.*是贪婪的。虽然你可以认为这(word2)?是贪婪的,因此必须回溯到,答案是否定的,因为第一个.*抓住了所有的字符串,然后引擎倒退寻找匹配。由于(word2)?匹配一个空字符串,它总是匹配,并且word3从字符串的末尾开始匹配。请参阅此演示并检查正则表达式调试器部分。

你想过,让我们对第一个.\*?使用惰性匹配。的问题(word1).*?(word2)?.*(word3)(即匹配word2与第二.*即贪婪)是有点不同的,因为它可以匹配可选的组。如何?第一个.*?匹配零个字符,然后尝试匹配所有后续子模式。因此,它找到了word1,然后是一个空字符串,并且在 之后没有找到word2正确的word1如果word2是在之后word1,就会与第一个匹配.*?请参阅此演示

解决方案

目前我看到了两种解决方案,它们都包括使第二个可选组对模式的其余部分“独占”,以便正则表达式引擎在找到时无法跳过它。

  • 分支复位 由卡西米尔提供溶液的上方。它的缺点是它不能移植到许多其他不支持分支重置的正则表达式风格。请参阅原始答案中的说明。
  • 使用温和的贪婪令牌(word1)(?:(?!word2).)*(word2)?.*?(word3)。它的效率低于分支重置解决方案,但可以移植到 JS、Python 和大多数其他支持前瞻的正则表达式风格。这是如何运作的?(?:(?!word2).)*匹配 0+ 次出现的任何字符,而不是换行符(带有/s,甚至包括换行符)且不以文字字符序列 开头word2。如果w匹配,则不能跟在后面ord2让构造匹配。因此,当它到达 时word2,它会停止并让后续的子模式(word2)?- 匹配并捕获以下内容word2为了使这种方法更有效*,请使用展开循环技术(word1)[^w]*(?:w(?!ord2)[^w]*)*(word2)?.*?(word3)