【问题标题】:Why Doesn't This Regex Lookahead Work?为什么这个正则表达式前瞻不起作用?
【发布时间】:2024-05-02 23:10:03
【问题描述】:

我正在设计一个用于某些 IIS 网址重写的正则表达式。目的是捕获以下网址:

  1. 不只是根目录中的文件(通过包含句点来标识),并且
  2. 不包含查询字符串,并且
  3. 不属于特定的子目录集,特别是“Account”和“Public”

我当前的正则表达式看起来像:

^(?!(Account)|(Public))([^./]+)(/[^?]*)?$

RegexPal 与以下测试集一起使用:

file.aspx
Account/otherfile.aspx
Public/otherfile.aspx
otherfolder1/otherfile.aspx?stuff=otherstuff
otherfolder2/otherfolder/otherfile.aspx
otherfolder3/
otherfolder4

我的正则表达式正确地忽略了前两种情况,但它仍然匹配第三种情况。这里的前瞻有什么问题?

【问题讨论】:

  • 这...对我来说似乎在 RegexPal 中按预期工作。您只希望示例中的最后 3 个匹配,对吗?
  • 正确。对我来说,它匹配 2、3、5、6 和 7。
  • 好吧,这真的很奇怪。我在实际测试中将示例隔开 - 在每个示例之间放置一个空行。如果我删除空行,它确实会给出预期的结果。
  • 是的,我不完全确定那里发生了什么。对于它的价值,我在 C# 中做了一个快速测试,它也产生了正确的结果。
  • 只是为了记录(?!(Account)|(Public)) 是负前瞻,而不是后瞻。 AccountPublic 周围的括号不做任何事情,因为前瞻不参与匹配。我相信应该是这样的:(?!Account|Public)。否则,正则表达式似乎确实有效,尽管在 RegExpal 中没有

标签: regex iis url-rewriting negative-lookahead regex-lookarounds


【解决方案1】:

我忍不住尝试想出一些可以在 RegExPal 中工作的东西(没有成功 - 编辑:刚刚验证,这在 RegExPal 中确实有效)但我想我会把它扔掉还有另一种方法来做你需要的,这可能更容易理解:

^(?!Account|Public|[a-zA-Z_0-9]+\.)[a-zA-Z_0-9/.]+$

解释:

^                   # start
(?!                 # open a negative lookahead
Account|Public|     # ignore both Account and Public
[a-zA-Z_0-9]+\.     # ignore files in root (i.e., letters/numbers, followed by period)
)                   # close negative lookahead
[a-zA-Z_0-9/.]+     # now match anything with letters/numbers, periods and slashes, but no '?' (ignores URLs with query string)
$                   # end

【讨论】:

  • 我认为这需要root中的文件以句点结尾,这是不正确的。句点几乎永远不会在字符串的末尾;通常是三个字符,但有时更多有时更少。
  • @JeffreyBlake:不,这不是前瞻的工作方式。由于它是负前瞻,因此一旦遇到句点,它就会匹配并失败,这就是您想要的。期间不一定要结束。试试看。
  • JeffreyBlake:阅读@sln 的答案后,我可以看到 RegExPal 上发生了什么。您的正则表达式实际上将示例输入的最后三行匹配为一个匹配(即,所有三行构成一个匹配),并且 RegExPal 不会显示(颜色)匹配,除非您选中“多行锚点”。 sln 的回答解释了原因。我的答案或 sln 的答案都可以满足您的需要,但是您的正则表达式可能会在某些时候失败,因为它肯定会超出行尾。 sln 的答案可能会更好,因为它比我的更通用,但我会犹豫在生产环境中使用你的答案。
  • +1 这解决了这个问题。不过,我正在争论要接受哪个答案。这正在驱动的重定向系统实际上需要根文件夹和剩余的文件结构分开匹配,从中可以构造结果替换 url。这让我认为我最初拥有的可能是最好的(删除了前瞻中的额外括号)。
【解决方案2】:

RegexPal 很困惑,但真正的问题是正则表达式设计不正确。

不确定您要做什么,但是在使用多行模式和锚点时^$
在正则表达式中,除非您专门设计它,否则必须注意不要
溢出锚。这适用于贪婪/非贪婪量词。
当将负面的前瞻条件加入混合时,情况会变得更糟。

在这种情况下,它导致 RegexPal 发疯,显然在 ^ 之前回溯
无需重新评估 ^ 。不过,这可能不是 JavaScript 问题。

在您的消费类中添加非换行符可以解决所有问题。应该是
添加到两个类中。

^(?!Account|Public)[^./\n]+(?:/[^?\n]*)?$

【讨论】:

  • +1 用于解释问题发生的原因。实际上,换行问题不是问题,因为重定向系统正在处理单个 URL。
【解决方案3】:

正如sln 所报告的那样,RegexPal 中的这些测试的问题在于,运行多行测试会使多行组合在一起以创建一个匹配项,否则它们不应该这样做。

正则表达式适用于它旨在实现的目的。其实是矫枉过正。对于 IIS 重写和重定向,如果您使用 IIS URL Rewrite Module,您可以选择指定它接受或不接受匹配的条件。其中一些选项包括:

  • 项目不是物理文件
  • 项目不是物理目录
  • 项目匹配(或不匹配)辅助模式

这些将比负前瞻更完全地达到预期的效果。

【讨论】:

    【解决方案4】:

    也许您想使用^(?!Account|Public)([^\.\/]+\/[^\?]*)$ 正则表达式。

    看这里:http://ideone.com/q3lAv

    那么正确的 RegExpal 模式应该是 ^(?!Account|Public)([^\.\/]+\/[^\?\n]*)$


    [更新]

    文件名不必在其名称中包含点 .,另一方面,文件夹/目录名称可能在其名称中包含点 .,但如果您想在第 7 行也有一个肯定匹配,那么你应该使用^(?!Account|Public)([^\.\/]+(?:\/[^\?]*|[^\.\?]*))$ 模式,它也应该像 RegExPal 模式一样工作。

    看这里:http://ideone.com/VcmEP

    【讨论】:

    • 第 7 项不匹配。另外,我很确定你永远不需要转义/,也不需要在[] 中转义.
    • @JeffreyBlake - 转义/. 更安全,它是正则表达式的标准,因为某些语言需要这样做(例如Perl)。除此之外,为什么要匹配第 7 项?文件不需要在名称中包含点。但是......如果这就是您要寻找的,那么请参阅上面的更新答案。感谢您考虑我的回答。