pascal-lc

须知少日拏云志,曾许人间第一流。

0%

从正则表达式匹配 IP 地址谈起

从正则表达式匹配 IP 地址谈起

正则表达式 RegEx 是一种强大的字符串处理工具,在字符串处理中应用广泛。RegEx(Regular Expression)可以理解为匹配规则(Regular)的搜索模式。正则表达式匹配的不是字符串本身,而是一种字符串生成规则。凡是符合这一规则的字符串,都可以使用正则表达式检索获取。

介绍完正则表达式的概念,这里可以通过一些实践来体会正则表达式的强大与便捷。下面介绍如何使用正则表达式匹配 IP 地址,这里作为练习只处理 IPv4 地址。

待匹配字符串如下:

$ arp -a

接口: 192.168.216.1 --- 0x3
  Internet 地址         物理地址              类型
  192.168.216.255       ff-ff-ff-ff-ff-ff     静态
  224.0.0.22            01-00-5e-00-00-16     静态
  224.0.0.251           01-00-5e-00-00-fb     静态
  224.0.0.252           01-00-5e-00-00-fc     静态
  239.255.255.250       01-00-5e-7f-ff-fa     静态

接口: 169.254.86.111 --- 0xd
  Internet 地址         物理地址              类型
  169.254.255.255       ff-ff-ff-ff-ff-ff     静态
  224.0.0.22            01-00-5e-00-00-16     静态
  224.0.0.251           01-00-5e-00-00-fb     静态
  224.0.0.252           01-00-5e-00-00-fc     静态
  239.255.255.250       01-00-5e-7f-ff-fa     静态

接口: 192.168.1.2 --- 0xe
  Internet 地址         物理地址              类型
  192.168.1.1           18-13-2d-2f-fa-b4     动态
  192.168.1.255         ff-ff-ff-ff-ff-ff     静态
  224.0.0.22            01-00-5e-00-00-16     静态
  224.0.0.251           01-00-5e-00-00-fb     静态
  224.0.0.252           01-00-5e-00-00-fc     静态
  239.255.255.250       01-00-5e-7f-ff-fa     静态
  255.255.255.255       ff-ff-ff-ff-ff-ff     静态

接口: 169.254.252.27 --- 0x14
  Internet 地址         物理地址              类型
  169.254.255.255       ff-ff-ff-ff-ff-ff     静态
  224.0.0.22            01-00-5e-00-00-16     静态
  224.0.0.251           01-00-5e-00-00-fb     静态
  224.0.0.252           01-00-5e-00-00-fc     静态
  239.255.255.250       01-00-5e-7f-ff-fa     静态
  255.255.255.255       ff-ff-ff-ff-ff-ff     静态

初级

根据 IPv4 地址的格式,可以发现,IP 地址有 4 组数字组成,每组数字长度范围为从 1 位到 3 位,并且每组数字之间由 . 分割。那么根据这一规律,可以使用正则表达式写出匹配字符串:

((\d{1,3})\.){3}(\d{1,3})
(\d{1,3}\.){3}(\d{1,3})

IP 地址由 4 个字节组成,每个字节 8 位,所以 IPv4 地址的取值范围是 0 ~ 255,共 256 个。

这样的处理方法虽然简单,一目了然,但却需要面对一个大问题——正则表达式匹配出来的不是 IPv4 地址。例如:

999.888.777.666
......

这些非法 IPv4 地址字段也会在这一不严谨的正则表达式中匹配出来,这显然不是我们想要的。因此,我们需要对正则表达式进行改进,使其只匹配合法的 IPv4 地址。

高级

在构造一个正则表达式的时候,一定要考虑周全,避免出现匹配到不合法的字段,定义清楚的规则,确定可以匹配什么,以及不能匹配什么。

在这个 IPv4 地址匹配的过程中,我们需要确定一个合法有效的 IPv4 地址的格式,即:

  1. 4 组数字,每组数字范围为 1 ~ 255;
  2. 每组数字之间用 . 分割;
  3. 任意的 1 位或 2 位数字
  4. 任意的以 1 开头的 3 位数字;
  5. 任意的以 2 开头,第二位数字在 04 之间的 3 位数字;
  6. 任意的以 2 开头,第二位数字为 5,第三位数字在 05 之间的 3 位数字;

知晓上述规则后,依次罗列所有规则,准确的匹配模式自然可以构造如下:

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

其中,按照 IPv4 地址规则,

  • (\d{1,2}) 匹配任意 1 位或 2 位数字;
  • (1\d{2}) 匹配任意以 1 开头的 3 位数字;
  • (2[0-4]\d) 匹配任意以 2 开头,第二位数字在 04 之间的 3 位数字;
  • (25[0-5]) 匹配任意以 25 开头的 3 位数字;

看起来不错,完全按照上述 IPv4 规则匹配,但只要实际匹配查找,会发现上述正则表达式,根本无法正常匹配 IPv4 地址,效果甚至不如第一个匹配模式。例如:

255.255.255.0
255.255.255.10
255.255.255.255

这两种 Ipv4 地址,上述正则表达式只能匹配到第一个和第二个,却无法匹配到第三个。但是,正则表达式规则是严格按照 IPv4 地址规则定义的,为什么会出现这种情况呢?

这就要引出正则表达式的贪心(greedy)特性了。也就是在正则表达式中,如果出现多个匹配模式,那么会优先匹配最长的匹配模式,而不是符合匹配规则的最短的,而且会尽量满足第一个匹配模式,具体到 IPv4 地址匹配,就是尽可能匹配最长的规则以及首位规则。

((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

这是匹配一组 IPv4 地址字段的正则表达式匹配模式,其中 (\d{1,2}) 表示匹配任意 1 ~ 2 位数字,在 IPv4 前三组字段的匹配中,由于 . 的存在,根据正则表达式的贪心特性,会尽可能匹配最长的匹配模式,即 (\d{1,2}) 无法匹配的会自动切换下一子表达式,以求匹配的字符串模式更长。所以,上述正则表达式匹配前三组没有出现任何问题,但是在第四组字段的匹配过程中,由于 (\d{1,2}) 无法匹配 255,所以当 (\d{1,2}) 匹配 25 时,两个数字全部匹配到,匹配模式自动结束,如果第四个字段是 3 个数字,那么第三个数字将被忽略而无法匹配到。

那么,如何改进呢?

进阶

其实,根据以上分析,可以发现,无需较大修改,因为 IPv4 规则仍是不变的,如果将 IPv4 地址的匹配规则改为如下,就可以完美匹配了。

(((25[0-5])|(2[0-4]\d)|(1\d{2})|(\d{1,2}))\.){3}(((25[0-5])|(2[0-4]\d)|(1\d{2})|(\d{1,2})))

按照正则表达式的贪心特性以及首位子表达式优先匹配特性,可以在规则组合时,优先选择匹配位数较长的子表达式,当前子表达式不满足时,在依次切换匹配位数较短的子表达式。

写在最后

本来只是正则表达式匹配 IPv4 地址,一个很简单的小练习,没想到发现了一个正则表达式匹配 IPv4 地址的坑,涉及到正则表达式的贪心特性以及首位子表达式优先。世事洞明皆学问,此言不虚,所以,还是记录一下吧。

欢迎关注我的其它发布渠道