正则表达式负先行被忽略

Regex Negative Lookahead Is Being Ignored

我有以下示例文本

[Item 1](./path/notes.md)
[Item 2](./path)
[Item 3](./path/notes.md)
[Item 4](./path)

当我应用以下正则表达式 \[(.*)\].*(?!notes\.md).*\) 时,我希望在打印第一个捕获组时得到以下输出

Item 2
Item 4

但我最终得到的是

Item 1
Item 2
Item 3
Item 4

对我来说,似乎负前瞻部分 (?!notes\.md) 由于某种原因被忽略了,所以正则表达式匹配了整个字符串。

真正让我感到困惑的是,积极的前瞻性工作正如我所期望的那样。例如,在打印第一个捕获组时使用 \[(.*)\].*(?=notes\.md).*\) returns 以下内容

Item 1
Item 3

这是有道理的,所以我真的很困惑为什么负先行不能正常工作。

这里的问题是 .* 在负前瞻之前是贪婪的,会继续寻找任何东西然后停止。

一种管理方法是将这种贪婪行为包含在负面前瞻中,例如 hère

https://regex101.com/r/yzUQoP/1

/\[(.*)\](?!.*notes\.md)/gm

简而言之,您的 .* 太多了(可能导致 Catastrophic backtracking,查一下!)。请记住,它们与任何字符匹配零次或多次。这意味着他们将继续尝试匹配,直到获得成功。而且这不一定是你想要的字符数。

解决你的问题的一个方法是把消极的展望移到前面,像这样:

(?!.*notes\.md)\[([^\]]+)\].*

Explanation:

(?!.*notes\.md) 后跟 'notes.md'

的任意数量的任意字符的否定前瞻

\[一个[字符

([^\]]+) 第 1 组,任何字符不是 ],一次或多次

\]一个]字符

.* 余下的行

使用“multiline”标志获取每一行。

让我们来看看在第 1 项上匹配您的模式时会发生什么:

  • \[(.*)\] 匹配 [Item 1]
  • .* 匹配 (./path/notes.md
  • 剩下的字符串现在是 )
  • (?!notes\.md) 检查剩余字符串是否与模式 notes\.md 匹配。它没有,所以比赛继续。
  • \)匹配),匹配成功。

如果您更改它,使先行之前的 .* 位于先行内部 (\[(.*)\](?!.*notes\.md).*\)),它现在将按如下方式工作:

  • \[(.*)\] 匹配 [Item 1]
  • 剩下的字符串现在是 (./path/notes.md)
  • (?!.*notes\.md) 检查剩余字符串是否与模式 .*notes\.md 匹配,因此匹配失败(更准确地说,正则表达式引擎将在放弃之前尝试回溯匹配,但没有其他方法可以匹配\[(.*)\]',所以匹配仍然失败。

因此,通过该更改,它将拒绝 notes.md 出现在 ) 之前任何位置的所有字符串。如果您希望它仅拒绝 notes.md 直接出现在 ) 之前的实例,您可以改用后向(没有 .*)或将 \) 添加到前向。

您尝试的模式 \[(.*)\].*(?!notes\.md).*\) 从第一个 [ 匹配到最后一个 ]

然后发生的是 .* 将匹配该行的其余部分,因此以下断言 (?!notes\.md) 将始终为真,因为该行的其余部分已经匹配。

然后引擎可以回溯匹配最后一个 )


如果你不想在匹配时交叉 []():

\[([^][]+)]\((?![^()]*\bnotes\.md\b)[^()]*\)
  • \[ 匹配 [
  • ([^][]+) 捕获 组 1,匹配 0+ 次除 []
  • 之外的任何字符
  • ]\( 匹配 ](
  • (?! 否定前瞻
    • [^()]*\bnotes\.md\b 匹配 0+ 次除 () 之外的任何字符,然后在单词边界之间匹配 notes.md 以防止部分匹配
  • ) 关闭前瞻
  • [^()]* 匹配除 ()
  • 之外的任何字符 0+ 次
  • \) 匹配 )

Regex demo