为什么这个正则表达式的前瞻不起作用?

4
我正在设计一个正则表达式,用于在一些IIS Url Rewrites中使用。目的是捕获以下类型的URL:
  1. 不仅仅是根目录下包含一个句点的文件
  2. 不包含查询字符串
  3. 不属于特定的子目录集,即“账户”和“公共”
我的当前正则表达式如下:
^(?!(Account)|(Public))([^./]+)(/[^?]*)?$

使用RegexPal测试集:

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

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

1
这个...在RegexPal中似乎按预期工作。你只想让你的示例中的最后3个匹配,对吗? - Mike Park
正确。对我来说,它匹配2、3、5、6和7。 - Jeffrey Blake
好的,这真的很奇怪。在我的实际测试中,我已经将示例分开 - 在每个示例之间放置了一个空行。如果我删除空行,它确实会给出所需的结果。 - Jeffrey Blake
是的,我也不完全确定发生了什么。不过值得一提的是,我在 C# 中进行了快速测试,它也产生了正确的结果。 - Mike Park
2
仅供参考,(?!(Account)|(Public)) 是负向前瞻,而不是后顾。而且括号中的 AccountPublic 并没有起到任何作用,因为前瞻不参与匹配。我认为应该像这样:(?!Account|Public)。否则,正则表达式似乎确实可以工作,但在 RegExPal 中无法工作。 - alan
4个回答

3

我忍不住想试着找出一些在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

我认为这需要根目录中的文件以句点结尾,这是错误的。句点几乎永远不会在字符串末尾;通常是在第三个字符处,但有时更多,有时更少。 - Jeffrey Blake
@JeffreyBlake:不,这不是前瞻的工作方式。由于它是负向前瞻,一旦遇到句号,它就会匹配并失败,这正是你想要的。句号不必在结尾处。试一下就知道了。 - alan
JeffreyBlake:在阅读了@sln的答案后,我可以看到RegExPal上发生了什么。您的正则表达式实际上将样本输入的最后三行作为一个匹配(即所有三行构成单个匹配),而RegExPal不会显示(着色)匹配,除非您选中“多行锚定”。sln的答案解释了原因。我的答案或sln的答案都能满足您的需求,但是您的正则表达式可能会在某些情况下失败,因为它肯定会超出行尾。sln的答案可能更好,因为它比我的更通用,但我会犹豫使用您的正则表达式在生产环境中。 - alan
+1是因为这解决了问题。然而我一直在考虑哪个答案应该被接受。实际上,这个重定向系统需要根文件夹和剩余文件结构分别匹配才能构建出最终的替换URL。这让我觉得最初的方法可能是最好的(只需移除先行断言中多余的圆括号即可)。 - Jeffrey Blake

1
根据sln的报道,RegexPal中这些测试的问题在于运行多行测试时,多行会组合在一起创建一个单一匹配,而实际上它们不应该这样做。
对于其设计目的,正则表达式是可以胜任的。实际上,它有点过头了。对于IIS重写和重定向,如果您使用IIS URL Rewrite Module,您可以选择指定条件来接受或拒绝匹配。其中一些选项包括:
- 项目不是物理文件 - 项目不是物理目录 - 项目与次要模式匹配(或不匹配)
这些选项将比负向先行断言更完全地实现所需的效果。

1

RegexPal 出现混乱,但实际问题是正则表达式设计不正确。

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

在这种情况下,它导致 RegexPal 产生混乱,并显然在重新评估^之前回溯到 ^ 。尽管如此,这可能并不是 JavaScript 的问题。

在消耗类中添加非换行符可以解决所有问题。它必须添加到两个类中。

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

在实践中,换行符问题并不是问题,因为重定向系统正在处理单个URL。如果能解释问题发生的原因,那么加1分。 - Jeffrey Blake

0

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

请在这里查看:http://ideone.com/q3lAv

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


[更新]

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

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


第七项匹配失败。此外,我非常确定您永远不需要转义 /,并且在 [] 中不需要转义 . - Jeffrey Blake
@JeffreyBlake - 转义/.更安全,这也是正则表达式的标准用法,因为一些语言要求这样做(例如Perl)。除此之外,你为什么想让第7个项目匹配呢?文件名不需要有点。但是...如果这就是你想要的,那么请参见我上面更新的答案。感谢您考虑我的答案。 - Ωmega

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