任何人看到我的正则表达式端口号有什么问题?

时间:2010-09-15 06:06:54

标签: regex

我为端口号做了一个正则表达式(在你说这是一个坏主意之前,它会进入一个更大的URL的正则表达式,这比听起来要难得多。)

我的同事说这真的很糟糕,并不会抓住一切。我不同意。

我相信这件事可以捕捉0到65535之间的所有内容以及没有其他内容,,我正在寻找对此的确认。

单行版本(适用于计算机):

/(^[0-9]$)|(^[0-9][0-9]$)|(^[0-9][0-9][0-9]$)|(^[0-9][0-9][0-9][0-9]$)|((^[0-5][0-9][0-9][0-9][0-9]$)|(^6[0-4][0-9][0-9][0-9]$)|(^65[0-4][0-9][0-9]$)|(^655[0-2][0-9]$)|(^6553[0-5]$))/

人类可读版本:

/(^[0-9]$)|                           # single digit
 (^[0-9][0-9]$)|                      # two digit
 (^[0-9][0-9][0-9]$)|                 # three digit
 (^[0-9][0-9][0-9][0-9]$)|            # four digit
 ((^[0-5][0-9][0-9][0-9][0-9]$)|      # five digit (up to 59999)
  (^6[0-4][0-9][0-9][0-9]$)|          #            (up to 64999)
  (^65[0-4][0-9][0-9]$)|              #            (up to 65499)
  (^655[0-2][0-9]$)|                  #            (up to 65529)
  (^6553[0-5]$))/                     #            (up to 65535)

有人可以确认我的理解是否正确(或其他方式)?

8 个答案:

答案 0 :(得分:26)

你可以大大缩短它:

^0*(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$
  • 无需每次重复锚点
  • 不需要很多捕获组
  • 无需拼写重复。

如果您不想允许前导零,请删除前导0*

这个正则表达式也更好,因为它首先匹配特殊情况(65535,65001等),从而避免了一些回溯。

哦,既然您说要将其用作更大的网址正则表达式的一部分,那么您应该将^$替换为\b(字边界锚点)


修改: @ceving询问是否真的需要重复6553655656。答案是否定的 - 你也可以使用嵌套的正则表达式,而不必重复那些前导数字。我们只考虑

部分
6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}

这可以改写为

6(?:[0-4][0-9]{3}|5(?:[0-4][0-9]{2}|5(?:[0-2][0-9]|3[0-5])))

我认为这使得正则表达式的可读性比现在更低。详细模式使差异更清晰。比较

6553[0-5]       |
655[0-2][0-9]   |
65[0-4][0-9]{2} |
6[0-4][0-9]{3}  

6 
(?:
 [0-4][0-9]{3}
|
 5
 (?:
  [0-4][0-9]{2}
 |
  5
  (?:
   [0-2][0-9]
  |
   3[0-5]
  )
 )
)

一些性能测量:针对1到99999之间的所有数字测试每个正则表达式,显示嵌套版本的最小可能不相关的性能优势:

import timeit

r1 = """import re
regex = re.compile(r"0*(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$")"""

r2 = """import re
regex = re.compile(r"0*(?:6(?:[0-4][0-9]{3}|5(?:[0-4][0-9]{2}|5(?:[0-2][0-9]|3[0-5])))|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$")"""

stmt = """for i in range(1,100000):
    regex.match(str(i))"""

print(timeit.timeit(setup=r1, stmt=stmt, number=100))
print(timeit.timeit(setup=r2, stmt=stmt, number=100))

输出:

7.7265428834649
7.556472630353351

答案 1 :(得分:8)

就个人而言,我只会匹配一个数字,然后我会用代码检查数字是否在范围内。

答案 2 :(得分:6)

嗯,很容易证明它将验证任何正确的端口:只生成每个有效字符串并测试它通过。确保它不允许任何它不应该更难的东西 - 显然你不能绝对测试每个无效的字符串。你绝对应该测试简单的情况和你认为可能错误传递的任何东西(或者使用较小的正则表达式错误传递的东西 - “65536”就是一个例子)。

它会允许一些略微奇怪的端口规格 - 例如“0000”。你想要允许前导零吗?

您可能还想考虑是否确实需要为每种情况单独指定^和$,或者是否可以使用^(case 1)|(case 2)|...$。哦,量词也可以简化“1到4位”的情况:([0-9]{1,4})会找到1到4位数。

(顺便说一下,你可能想要听起来有点不那么傲慢。如果你和其他人一起工作,那么以较不激进的方式进行交流可能会做更多的事情来改善每个人的一天,而不仅仅是证明你的正则表达式是正确...)

答案 3 :(得分:3)

样式说明:

一遍又一遍地重复[0-9]是愚蠢的 - 像[0-9][0-9][0-9]更像是\d{3}

答案 4 :(得分:3)

将其解析为数字并使用整数比较有什么问题? (无论这是否会成为“更大”的正则表达式的一部分)。

如果我使用正则表达式,我只会使用:

\d{1,5}

不,它不检查“有效”端口号(也不是你的)。但它更清晰,而且出于实际目的,我会说它“足够好。”

PS:我会努力让自己更谦虚。

答案 5 :(得分:2)

/^(6553[0-5])|(655[0-2]\d)|(65[0-4]\d{2})|(6[0-4]\d{3})|([1-5]\d{4})|([1-9]\d{1,3})|(\d)$/

答案 6 :(得分:0)

正则表达式有很多实现,什么是平台。尝试以下,删除空白

^[1-5]?\d{1,4}|6([0-4]\d{3}|5([0-4]\d{2}|5([0-2]\d|3[0-5]))$

可读

^
[1-5]?\d{1,4}|
6(
 [0-4]\d{3}|
 5(
  [0-4]\d{2}|
  5(
   [0-2]\d|
   3[0-5]
  )
 )
$

答案 7 :(得分:0)

我会使用this一个:

6(?:[0-4]\d{3}|5(?:[0-4]\d{2}|5(?:[0-2]\d|3[0-5])))|(?:[1-5]\d{0,3}|[6-9]\d{0,2})?\d

以下Perl脚本测试了一些数字:

#! /usr/bin/perl

use strict;
use warnings;

my $port = qr{
6(?:[0-4]\d{3}|5(?:[0-4]\d{2}|5(?:[0-2]\d|3[0-5])))|(?:[1-5]\d{0,3}|[6-9]\d{0,2})?\d
}x;

sub test {
  my ($label, $regexp, $start, $stop) = @_;
  my $matches = 0;
  my $tests = 0;
  foreach my $n ($start..$stop) {
    $tests++;
    $matches++ if "$n" =~ /^$regexp$/;
    $tests++;
    $matches++ if "0$n" =~ /^$regexp$/;
  }

  print "$label [$start $stop] => $matches matches in $tests tests\n";
}

test "Port", $port, 0, 2**16;

输出结果为:

Port [0 65536] => 65536 matches in 131074 tests