跳到主内容
d.devtul.fun
EN
正则 · IPv4

IPv4 正则

一个正确的 IPv4 正则,把每一段都卡在 0-255,外加为什么范围校验该写在代码里、而不是正则里。

正则模式

^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$

匹配四个点分隔的段,每段都在 0-255。它会拒绝 256 和 999,对前导零只部分拦截——关于八进制请看下文。

在线测试这个正则

在下面直接改文本 —— 匹配在本机完成,不会上传任何内容。

其它写法

放行前导零
^(25[0-5]|2[0-4]\d|1\d\d|[0-9]?\d?)(\.(25[0-5]|2[0-4]\d|1\d\d|[0-9]?\d?)){3}$

允许 01 和 001。对宽松输入方便,但如果解析器把它当八进制就危险了。

带可选 CIDR
^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}(/(3[0-2]|[12]?\d))?$

加一个可选的 /0-32 后缀表示网段。仍是形状校验,不是路由校验。

日常用的 IPv4 正则

错误的 IPv4 正则到处都是,因为它短。正确的那条长一点,因为它真的把每个数字框住了。下面这条是我信得过的单地址版本:

^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$

四段,每段用点分开,每段都强制落在 0 到 255。不多不少。

为什么 \d{1,3} 是错的

最诱人的捷径是 \d{1,3}(\.\d{1,3}){3}。它看着对,错得狠。一个一到三位数字的单段会接受 999,于是这条正则会乐呵呵地匹配 999.999.999.999,而那根本不是 IP 地址。更糟的是,它接受 256.1.1.1,尽管 256 已经超范围。一旦你在乎这个值是不是真地址,这条正则就在骗你。

正则接受 999.999.999.999接受 256.1.1.1结论
\d{1,3}(\.\d{1,3}){3}是是错
[0-9]{1,3}(\.[0-9]{1,3}){3}是是错
上面那条按段框定的否否对

拆开看段的正则

每段由几个互斥的备选拼成,按顺序尝试:

  • 25[0-5] 覆盖 250 到 255。
  • 2[0-4]\d 覆盖 200 到 249(十位是 0-4,个位随意)。
  • 1\d\d 覆盖 100 到 199。
  • [1-9]?\d 覆盖 0 到 99,它要求首位可省但非零,于是既放 0 和 7,又在该较真的地方挡掉多余的前导零。

备选从高到低排,引擎先匹配最大的合法数。四段是"一组加前导点"重复三次,两头再包住,不让任何多余字符混进来。

前导零的坑

哪怕数值对了,还有一层语义坑:010.1.1.1。在不少 C 和 Unix 解析器里,前导零表示八进制,所以 010 是十进制的 8,不是 10。上面那条严格正则会拒绝 010,因为段的备选从 [1-9]?\d 起,多位数字的前导零被禁掉。如果你的下游用了 C 风格解析器,这种拒绝正是你想要的。要是你放行前导零,得写清楚——同一串在不同系统里是不同的数。

这个正则匹配了什么、漏了什么

输入期望说明
192.168.1.1接受私有段,教科书样例
127.0.0.1接受回环地址
256.1.1.1拒绝首段超范围
999.999.999.999拒绝四段全超范围
01.2.3.4严格版拒绝前导零被视为八进制风险

CIDR 后缀与私有段

单地址常常不够,你可能需要 10.0.0.0/8 这种网段。下面这个变体加了可选前缀长度:

^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}(/(3[0-2]|[12]?\d))?$

末尾的 (/...)? 允许一个斜杠加 0 到 32 的数。那描述的是一个网络,不是一台主机,所以别喂给期望单地址的代码。私有段检查要在解析后拿整数去比对 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16,而不是用正则。

为什么范围校验该写在代码里

核心论点:用正把 0-255 表达出来既丑又脆,而且它依然只证明这串文字"像地址"。老实做法是拆开字符串、拿整数比:

def is_ipv4(s):
    parts = s.split(".")
    if len(parts) != 4:
        return False
    for p in parts:
        if not p.isdigit() or not 0 <= int(p) <= 255:
            return False
    return True

这更好懂,一次比较就拒掉 256 和 999,而且如果你显式禁掉前导零,还能躲开八进制歧义。正则适合快速 grep 或表单提示;凡是你要拿它分支判断的,就解析它。

另外,整数比较还能顺手做私网判断:把四段拼成 32 位整数后,和 10.0.0.0/8 之类的段做位与,一眼就知道是不是内网。这种事正则连边都摸不着,却是安全代码天天要干的活。

怎么测你的 IPv4 正则

把表中的样例跑一遍,再补上 0.0.0.0、255.255.255.255 和只有三段的 1.2.3。如果你的正则会放过 256.1.1.1、999.0.0.1 或三段串,先修好段的备选再信它。而如果这个值要驱动网络决策,就转成整数去比;让正则当门房,别当法官。多写一行整数比较,往往比多写十行正则更值当。

八进制、十进制,以及你的解析器怎么假设

同样四个数字,在不同读者眼里意思可能不同。严格正则会拒绝 010.0.0.1 来躲开八进制坑;但如果你放宽它,就得知道代价:C 库可能把 010 读成 8,而像 Python 这样的语言会读成 10,浏览器则可能直接拒掉。这些正则都看不见。稳妥的设计是:在校验里禁掉前导零,然后用显式的十进制去转换每一段。这样在到达系统调用之前就消除了歧义,让你接受的每个地址都只有一个可预期的含义。

顺带一提,有些老系统还认 0x 开头的十六进制,0x7f.0.0.1 在它们看来就是 127.0.0.1。正则更管不到这种写法。所以一句话收尾:带数值含义的地址,交给会算数的代码,别让只会数字符的正则拍板。

常见问题

正确的 IPv4 正则是哪条?

把每段用 25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d 框住、重复四次的那种。它拒绝 256 和 999。

为什么 \d{1,3} 用来校验 IP 是错的?

它放行 999 和 256,那都不是合法段,于是会匹配根本不是 IP 的字符串。

IPv4 里允许前导零吗?

严格正则会拒绝,因为 010 在 C 风格解析器里可能是八进制的 8。放行只应在你写清行为时才做。

IP 段要用正校验吗?

不要。把四段解析成整数再去比范围;正则只查形状。

其它正则速查

打开完整的正则测试器