为什么 "abcdef" 不符合 (?=abc)def,但符合 abc(?=def)?

7
在Javascript中,我有一个字符串abcdef,但无法理解这种奇怪的行为:
  • (?=abc)def不匹配该字符串
  • abc(?=def)匹配该字符串
为什么呢?

也许你想使用 (?:abc)def,它使用的是非捕获组而不是正向先行断言。 - DaoWen
@DaoWen 不是的,我已经尝试过了... 试试 "abcdef".replace(/(?:abc)def/, "") 它会替换整个字符串。 - Zaffy
4个回答

19
在正则表达式中,(?=abc)表示捕获零宽度,即在成功匹配后不会将输入字符串中的光标向前移动。这个构造简单地说是看一下接下来的三个字符是否是abc,如果是,然后检查这些字符是否为def。此时匹配失败。
要完成匹配,您需要了解正则表达式引擎的工作原理。考虑输入字符串abcdef和正则表达式abc(?=def)。引擎首先匹配a,然后将光标移到输入字符串中的下一个字符,并尝试匹配b。因为光标位于输入字符串的b上,所以匹配成功。然后,引擎在输入字符串内部移动光标,并尝试匹配c。由于光标位于输入字符串的c上,所以匹配成功,并且再次将输入字符串中的光标移动到下一个字符。现在,引擎遇到了(?=def)。此时,引擎只是向前查看,以查看从光标所在位置开始的下一个三个字符是否实际上是def,而不移动光标,发现它们是的,因此匹配成功。
现在考虑输入字符串xyz和正则表达式x(?=y)Z。正则表达式引擎将光标放置在输入字符串的第一个字母上,并检查它是否为x,发现它是,所以将光标移到输入字符串的下一个字符。现在它向前查看,以查看下一个字符是否为y,发现确实是,但是引擎不会将输入文本光标向前移动,因此输入文本中的光标仍停留在y上。接下来,引擎查看光标是否位于字母z上,但由于输入文本中的光标仍然位于y上,因此匹配失败。您可以在http://www.regular-expressions.info/lookaround.html上阅读更多关于正向和负向先行断言的内容。

还没有明白。如果它必须是“在某事之后”,请尝试在开头添加^,结果将是相同的。 - Zaffy
1
更新了解释,涵盖了正则表达式引擎如何处理匹配成功以及展示如果前瞻插入到示例表达式的中间会发生什么。关键在于跟踪光标在输入文本中的位置。 - Ro Yo Mi
1
太棒了的解释! - user1993

4

(?=...)是一个向前查找,也就是测试字符串右侧的内容。注意,向前查找是一种零宽断言,不会消耗字符。在您的第一个示例中:(?=abc)表示必须在abc之后遇到def。这就是模式失败的原因。

在您的第二个示例中,它在abc之后找到了def,然后匹配了字符串。


1
非常清晰地解释了零宽断言,还解释了为什么 /(?=def)def/.test('abcdef') 匹配。 - Fabrício Matté

2

MDN JavaScript中的先行断言定义

x(?=y)
当且仅当'x'后跟'y'时,匹配'x'。这称为先行断言。

例如,/Jack(?=Sprat)/仅在其后跟'Sprat'时匹配'Jack'。/Jack(?=Sprat|Frost)/仅在其后跟'Sprat'或'Frost'时匹配'Jack'。然而,'Sprat'和'Frost'都不是匹配结果的一部分。

因此,如果(?=y)前面有另一个语句,即空字符串,在此情况下,仅当第一个语句后跟第二个语句时才会匹配。 如果没有前导语句,则表达式(?="abc")将在前3个字符abc上匹配而不捕获它们,然后再次检查这些字符是否为def,这将失败。


2

根据您对我的评论的回复,我认为您想要的是一个正向后顾

(?<=abc)def

编辑:

既然您正在使用JavaScript(抱歉,我只看了您的问题,没有看标签),为什么不只使用一个普通的捕获组,并将匹配项包含在替换模式中呢?

"abcdef".replace(/(abc)def/, "$1")

当然,这在JavaScript中是经典的,但是它没有向后查找。 - Zaffy
因为使用 /g 替换时会跳过已替换的文本。例如,"a1a2a".replace(/(a)(\d)(a)/g, "$1b$2b$3"); 将得到 "ab1ba2a" 而不是 "ab1bab2ba"。 - Zaffy
@Zaffy - 只需将 look-behind 替换为捕获组,而不是 look-ahead,例如:"a1a2a".replace(/(a)(\d)(?=a)/g, "$1b$2b") // => "ab1bab2ba" - DaoWen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接