这个正则表达式如何匹配FQDNs(不包括.arpa)?

3

我正在尝试理解正则表达式的工作原理。我已经逐渐了解它的一些内容。然而,我并没有完全理解这个正则表达式。它基本上是一个针对完全合格域名的正则表达式,但是要求结尾不能是.arpa

(?=^.{4,253}$)(^([a-zA-Z0-9]{1,63}\.)+[a-zA-Z]{2,63}[^.arpa]$)

https://regex101.com/r/hU6tP0/3

这个正则表达式不匹配 google.uk。如果我将其更改为:

(?=^.{4,253}$)(^([a-zA-Z0-9]{1,63}\.)+[a-zA-Z]{1,63}[^.arpa]$)

它再次起作用了。

但是这个也可以正常工作

(?=^.{4,253}$)(^([a-zA-Z0-9]{1,63}\.)+[a-zA-Z]{2,63}$)

以下是我对以下内容的思考过程:

这里是我的思维过程

?=^.{4,253}$)(^([a-zA-Z0-9]{1,63}\.)+[a-zA-Z]{2,63}[^.arpa]$)

我理解为这样。
(?=

正向先行断言(有人能解释一下这到底是什么意思吗?)据我理解,它只是意味着字符串需要与正则表达式匹配。

^.{4,253}$)

匹配所有字符,但长度必须介于4到253个字符之间。

(^([a-zA-Z0-9]{1,63}\.)

开始一个捕捉组,并在其中创建另一个捕捉组。这个捕捉组表示每个非特殊字符可以重复1到63次,或直到写入.为止。

+

先前的捕获组可以无限重复,但必须以“.”结尾。这样才能启动下一个捕获组。
[a-zA-Z]{2,63}

然后,您可以任意多次使用大写字母A到Z进行书写,但是它必须介于2到63之间。

[^.arpa]$)

最后一个字符不能是.arpa
有人能告诉我哪里出了问题吗?

+[a-zA-Z]{1,63} 可以匹配到 Google URL 中长度为 2 的 'UK',但是 +[a-zA-Z]{2,63} 无法匹配到它,因为 'UK' 的长度不在 2 到 63 个字符之间。 - Joshua Duxbury
1
当我去掉 [^.arpa] 时,它就可以了。 - jermey
测试了我的想法,你是正确的。就像 [^.arpa] 会在字符串中添加一个额外的字符一样,我在先前的评论中犯了错误。 - Joshua Duxbury
2个回答

4

这个并不像你想的那样工作:

[^.arpa]

所有这句话的意思就是“以不属于字母apr.中的任何一个结尾”——这是一个否定的字符类。
你可能在想负预测断言
(?!\.arpa)$

但是,如果您试图在正则表达式中组合多个条件,我建议您可能正在使用错误的工具。由于贪婪/非贪婪匹配等原因,它变得复杂且难以调试。
您的“正/负”前瞻是为了匹配不被其他模式包围的模式片段。但是,如果您匹配可变宽度,这可能会产生一些意外的结果,因为正则表达式引擎会回溯直到找到与之匹配的内容。
一个更简单的例子:

([\w.]+)(?!arpa)$

适用于:
www.test.arpa

它会匹配吗?组里有什么?

...它会匹配,因为[\w\.]+将消耗所有内容,然后前瞻不会“看到”任何东西。

如果您使用:

([\w]+)\.(?!arpa)

然而,您将捕获www,但是如果使用例如g标志,您将不会匹配test(因为www后面没有.arpa,但是test有)。

https://regex101.com/r/hU6tP0/5

使用负断言作为模式确实会使事情变得复杂。我建议不要这样做,而是应用两个单独的测试。这对你来说很难理解,对未来的维护程序员也很难。


我明白你的意思。目前我正在尝试在HTML的模式字段中使用它,据我所知,除了可能耦合另一个事件处理程序以外,我没有其他方法来解决这个问题。如果您有任何建议如何处理这个精确的问题,那就太好了,但我理解您的观点。 - jermey

3
这是关于你正则表达式的分析:
(?=^.{4,253}$)              # force min length: 4 chars, max length: 253 chars
(                           # Capturing Group 1 (CG1) - not needed
    ^                       # Match start of the string
    (                       # CG2 (can be a non capturing group '(?:...)')
      [a-zA-Z0-9]{1,63}     # any sequence of letters and numbers with length between 1 and 63
      \.                    # a literal dot
    )+                      # CLOSE CG2
    [a-zA-Z]{1,63}          # any letter sequence with length between 1 to 63 
    [^.arpa]                # a negated char class: any char that is not a "literal" '.','a','r','p' (last 'a' is redundant)
  $                         # end of the string
)                           # CLOSE CG1

为了避免字符串的尾部成为.arpa,您需要使用负预查(?!...),因此应该像这样进行修改:
(?=^.{4,253}$)(?!.*\.arpa$)(^([a-zA-Z0-9]{1,63}\.)+[a-zA-Z]{2,63}$)

一个在线演示

更新:

我已经升级了正则表达式以使其更加合理(我还包括了Sobrique的建议,添加了一个重要的细节):

 /^(?=.{4,253}$)([a-z0-9]{1,63}[.])+(?!arpa$)[a-z]{2,63}$/i

压缩版在线演示

说明:

/                     # js regex delimiter
 ^                    # start of the string
   (?=.{4,253}$)      # force min length: 4 chars, max length: 253 chars
   (?:                # Non capturing group 1 (NCG1)
     [a-z0-9]{1,63}   # any letter or digit in a sequence with length from 1 to 63 chars
     [.]              # a literal dot '.' (more readable than \.)
   )+                 # CLOSE NCG1 - repeat its content one or more time
   (?!arpa$)          # force that after the last literal dot '.' the string does not end with 'arpa' (i've added '$' to Sobrique suggestion instead it prevents also '.arpanet' too)
   [a-z]{2,63}        # a sequence of letters with length from 2 to 63
 $                    # end of the string
/i                    # Close the regex delimiter and add case insensitive flag [a-z] match also [A-Z] and viceversa

var re = /^(?=.{4,253}$)([a-z0-9]{1,63}[.])+(?!arpa$)[a-z]{2,63}$/i;

var tests = ['google.uk','domain.arpa','domain.arpa2','another.domain.arpa.net','domain.arpanet'];
var m;

while(t = tests.pop()) {
    document.getElementById("r").innerHTML += '"' + t + '"<br/>';
    document.getElementById("r").innerHTML += 'Valid domain? ' + ( (t.match(re)) ? '<font color="green">YES</font>' : '<font color="red">NO</font>') + '<br/><br/>';
}
    
<div id="r"/>


谢谢,我看到了我的错误所在。问题是JavaScript不允许负向先行断言,所以我不知道如何在那种情况下使用它。你能给我一个不使用负向先行断言的例子吗? - jermey
多次测试是一个选项吗?因为说实话,这就是答案。 - Sobrique
我基本上是在使用它来处理HTML字段(模式属性),并使用一个处理验证的库。我可以添加事件监听器并手动执行此操作,但我宁愿避免这样做。 - jermey
@jermey:JavaScript支持负向先行断言(?!...)(但不支持正向后行断言(?<=...)或者负向后行断言(?<!...))。 - Giuseppe Ricupero
你说得对。我读得太快了。谢谢你的帮助。我把所有的正则表达式都放在一个位置,所以它仍然可以维护。我会选择你的答案,因为它给了我我想要的东西。但 Sobrique 也给了我很多见解,所以也值得一看。 - jermey
@Sobrique:我在我的更新中采纳了您的建议(以节省一些步骤)。我们可以使用最后一个点“.”作为良好的锚点来放置负向先行断言。它还需要添加“$”以满足要求(因此是“(?!arpa$)”)。如果是“(?!arpa)”,它也会拒绝匹配以“.arpanet”结尾的字符串。 - Giuseppe Ricupero

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