负前瞻正则表达式

Posted

技术标签:

【中文标题】负前瞻正则表达式【英文标题】:Negative lookahead Regular Expression 【发布时间】:2011-10-14 16:20:05 【问题描述】:

我想匹配所有以“.htm”结尾的字符串,除非它以“foo.htm”结尾。我通常对正则表达式很满意,但消极的前瞻让我很难过。为什么这不起作用?

/(?!foo)\.htm$/i.test("/foo.htm");  // returns true. I want false.

我应该改用什么?我想我需要一个“负面的behind”表达式(如果 javascript 支持这样的事情,我知道它不支持)。

【问题讨论】:

很遗憾,JavaScript 不支持正则表达式中的“lookbehind” 通常最好有一个带有一两个循环的更简单的正则表达式,而不是一个超级怪物(好吧,你想要的不是超级怪物,但代码有增长的趋势)需要我说不可维护的正则表达式。 这可能不及时,但要解释为什么这不起作用:您的正则表达式不是 0 宽度,这意味着在 javascript 中它转换为“匹配 '.htm'但如果它以“foo”开头,则不是,因为“.htm”永远不会以“foo”开头,这是行不通的。否定前瞻的意思是“此时,排除此处否定为真的匹配项”,但它实际上并不消耗字符串。 【参考方案1】:

这个问题真的很简单。这样就可以了:

/^(?!.*foo\.htm$).*\.htm$/i

【讨论】:

+1。不仅不需要lookbehind,而且如果它可用,它也不是最好的工具。 这么有用的技术! 你能解释一下发生了什么吗?我看到您有一个行首标记 (^),但有两个行尾标记 ($)。这如何使负前瞻工作? @ericbowden 如果您仍然想知道:它匹配字符串的开头,然后不匹配 .*foo\.htm 到字符串的结尾。因为没有消耗前瞻,所以它外面的第二个 $ 实际上是匹配的那个。【参考方案2】:

你所描述的(你的意图)是一个否定的look-behind,并且Javascript不支持look-behinds。

look-aheads 从放置它们的字符开始向前看——并且您已将其放置在. 之前。因此,您实际上是在说“任何以 .htm 结尾的内容,只要从该位置 (.ht) 开始的前三个字符不是 foo”,这始终是正确的。

通常情况下,否定后视的替代方法是匹配比您需要的更多,并且只提取您实际需要的部分。这是 hacky,根据您的具体情况,您可能会想出其他方法,但类似这样:

// Checks that the last 3 characters before the dot are not foo:
/(?!foo).3\.htm$/i.test("/foo.htm"); // returns false 

【讨论】:

你给了我足够的东西让我自己走完剩下的路。这适用于我所有的测试用例:/(^.0,2|(?!foo).3)\.htm$/i +1 很好的解释。但是,/(?!foo).3\.htm$/i 将无法匹配少于三个字符的名称,即a.htm。这是一个可以得到所有人的:/^(?!.*foo\.htm$).*\.htm$/i【参考方案3】:

如前所述,JavaScript 不支持否定的后视断言。

但您可以使用变通方法:

/(foo)?\.htm$/i.test("/foo.htm") && RegExp.$1 != "foo";

这将匹配以.htm 结尾的所有内容,但如果它匹配foo.htm,它会将"foo" 存储到RegExp.$1,因此您可以单独处理。

【讨论】:

MDN 报告 the RegExp.$1 feature is non-standard.【参考方案4】:

就像 Renesis 提到的,JavaScript 不支持“lookbehind”,所以也许只需组合使用两个正则表达式:

!/foo\.htm$/i.test(teststring) && /\.htm$/i.test(teststring)

【讨论】:

JavaScript 支持前瞻。 thx :) 只是记得一年前,可能我的记忆力不太好【参考方案5】:

String.prototype.endsWith (ES6)

console.log( /* !(not)endsWith */

    !"foo.html".endsWith("foo.htm"), // true
  !"barfoo.htm".endsWith("foo.htm"), // false (here you go)
     !"foo.htm".endsWith("foo.htm"), // false (here you go)
   !"test.html".endsWith("foo.htm"), // true
    !"test.htm".endsWith("foo.htm")  // true

);

【讨论】:

【参考方案6】:

这个答案可能比必要的时间晚了一点,但我会把它留在这里,以防有人现在遇到同样的问题(问这个问题后 7 年零 6 个月)。

现在,ECMA2018 标准中包含了lookbehinds,并且至少在 Chrome 的最新版本中得到了支持。但是,无论有没有它们,您都可以解决这个难题。

负前瞻的解决方案:

let testString = `html.htm app.htm foo.tm foo.htm bar.js 1to3.htm _.js _.htm`;

testString.match(/\b(?!foo)[\w-.]+\.htm\b/gi);
> (4) ["html.htm", "app.htm", "1to3.htm", "_.htm"]

消极后视的解决方案:

testString.match(/\b[\w-.]+(?<!foo)\.htm\b/gi);
> (4) ["html.htm", "app.htm", "1to3.htm", "_.htm"]

具有(技术上)积极前瞻的解决方案:

testString.match(/\b(?=[^f])[\w-.]+\.htm\b/gi);
> (4) ["html.htm", "app.htm", "1to3.htm", "_.htm"]

等等

所有这些 RegExps 以不同的方式告诉 JS 引擎同一件事,它们传递给 JS 引擎的消息如下所示。

请在此字符串中找出所有符合以下条件的字符序列:

与其他文本(如单词)分开; 由一个或多个英文字母、下划线、 连字符、点或数字; 以“.htm”结尾; 除此之外,“.htm”之前的序列部分可以是任何东西 而是“foo”。

【讨论】:

【参考方案7】:

你可以用类似的东西来模仿消极的后视 /(.|..|.*[^f]..|.*f[^o].|.*fo[^o])\.htm$/,但程序化方法会更好。

【讨论】:

以上是关于负前瞻正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式前瞻(?=)后顾(?<)负前缀(?!)负后顾(?<!)

Java 正则表达式:负前瞻

正则表达式忽略分组顺序匹配(前瞻后顾负前瞻负后顾的应用)

负前瞻 python 正则表达式

Prometheus(公制)使用逆正则表达式匹配/负前瞻重新标记配置

正则表达式?: ?! ?=