我有以下示例文本
[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).*\)
会返回以下内容
Item 1
Item 3
这是有道理的,所以我真的很困惑为什么负向前瞻功能不正常。
答案 0 :(得分:2)
简而言之,您有太多的 .*
(可能导致 Catastrophic backtracking
,查一下!)。请记住,它们匹配任何字符零次或多次。这意味着他们将继续尝试匹配,直到获得成功。这不一定是您想要的字符数。
解决问题的一种方法是将消极的目光移到前面,如下所示:
(?!.*notes\.md)\[([^\]]+)\].*
Explanation
:
(?!.*notes\.md)
否定前瞻任意数量的任何字符后跟“notes.md”
\[
一个 [
字符
([^\]]+)
第 1 组,任何不是 ]
的字符,一次或多次
\]
一个 ]
字符
.*
其余部分
使用“multiline
”标志获取每一行。
答案 1 :(得分:2)
让我们看看在项目 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
直接出现在 )
之前的实例,您可以使用后视(不带 .*
)或将 \)
添加到前瞻中。
答案 2 :(得分:0)
这里的问题是负前瞻之前的 .*
是贪婪的,会继续寻找任何东西然后停止。
管理这种情况的一种方法是将这种贪婪行为包含在像这里这样的负面预测中
https://regex101.com/r/yzUQoP/1
/\[(.*)\](?!.*notes\.md)/gm
答案 3 :(得分:0)
您尝试匹配的模式 \[(.*)\].*(?!notes\.md).*\)
从第一个 [
到最后一次出现 ]
然后会发生 .*
将匹配该行的其余部分,因此以下断言 (?!notes\.md)
将始终为真,因为该行的其余部分已经匹配。
然后引擎可以回溯匹配最后一个)
如果您不想在匹配时交叉 []
和 ()
:
\[([^][]+)]\((?![^()]*\bnotes\.md\b)[^()]*\)
\[
匹配 [
([^][]+)
捕获组 1,匹配除 [
和 ]
之外的任何字符 0 次以上]\(
匹配 ](
(?!
负前瞻
[^()]*\bnotes\.md\b
匹配 0+ 次除 (
和 )
之外的任何字符,然后在单词边界之间匹配 notes.md
以防止部分匹配)
关闭前瞻[^()]*
匹配除 (
和 )
之外的任何字符的 0 次以上\)
匹配 )