负前瞻正则表达式
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 报告 theRegExp.$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$/
,但程序化方法会更好。
【讨论】:
以上是关于负前瞻正则表达式的主要内容,如果未能解决你的问题,请参考以下文章
正则表达式前瞻(?=)后顾(?<)负前缀(?!)负后顾(?<!)