现在有一道题目,要求利用python中re模块来匹配ip地址,我们应如何着手?
首先能想到的是ip地址是数字,正则表达式是如何匹配数字的呢?
\d或[0-9]
对于这个问题,不要一下子上来就写匹配模式,应该一步步分解,把复杂的问题简单化
比如ip地址,我们可以总结一下规律
1. 它是一个字符串
2. 字符串内部是由4个1-3位的数字和3个.组成
3. 数字的范围是0-255
接下来,我们先试一下匹配第1个数字
第一步:尝试匹配192.168.100.123中的192
>>> importre>>> re.search(r"\d\d\d", "192.168.100.123")<_sre.SRE_Match object; span=(0, 3), match='192'>
第二步:尝试匹配192.168.100.123中的192.
值得注意的是,由于正则表达式中的元字符 . 表示除了\n之外的任意一个字符,我们需要匹配 . 本身,就需要用 \ 进行转义
>>> re.search(r"\d\d\d\.", "192.168.100.12")<_sre.SRE_Match object; span=(0, 4), match='192.'>
第三步:尝试匹配192.168.100.123的整体
>>> re.search(r"\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d", "192.168.100.123")<_sre.SRE_Match object; span=(0, 15), match='192.168.100.123'>
这样写有什么问题呢?
1. 我们的数字并不都是3位,像192.168.100.1这样的,我们的匹配模式就失效了
>>> re.search(r"\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d", "192.168.100.1")>>>
2. 不够美观
第四步:优化误区一
很多人一上手,就写成了[0-255],这不就是数字的范围0-255吗?我们说,这样是不对的
正则表达式中,真正要匹配的永远是字符串,一个字符串内部是由三位的数字构成的,如果需要匹配三位数字的形式,就需要用到[0-9][0-9][0-9]或\d\d\d,用一个[0-9]表示的只能匹配一位,[0-255]这种错误的写法也只能匹配到一位0-9之间的数字
>>> re.search(r"[0-255]", "255")<_sre.SRE_Match object; span=(0, 1), match='2'>
如下图,第一个[0-255]匹配到了1, \.匹配到了.
这个ip地址中根本没有 1. 这种形式的,所以返回了None
>>> re.search(r"[0-255]\.[0-255]\.[0-255]\.[0-255]", "192.168.100.1")>>>
优化误区二:
既然上面的不对,那能不能把255分解成 2, 5, 5, 我们匹配时能否写成[0-2][0-5][0-5],看似是没问题的,我们来试试
>>> re.search(r"[0-2][0-5][0-5]", "192.168.100.1")<_sre.SRE_Match object; span=(8, 11), match='100'>
>>>
为什么192没有匹配到?168也没有匹配到?因为数字的十位和个位最大只能是5,超过5的肯定没法匹配
第五步:继续优化
我们来看下0-255这个范围,当百位是0或者1时,十位和个位可以是[0-9],也就是\d,当百位是2时,十位是[0-4]时,个位可以是\d,当百位是2时,十位是5时,个位只能是[0-5],那么,我们是不是可以这样来写,比如匹配192,匹配模式可以写成
[01]\d\d|2[0-4]\d|25[0-5]
如果有重复的,我们可以给上面的模式加上 {n} 表示重复匹配前面的字符n次
>>> re.search(r"(([01]\d\d|2[0-4]\d|25[0-5]\d)\.){3}([01]\d\d|2[0-4]\d|25[0-5]\d)", "192.168.100.123")<_sre.SRE_Match object; span=(0, 15), match='192.168.100.123'>
看似正确了,但是还是匹配不到数字 1 ,因为我们的百位是[01],意味这如果是 1 的情况下,我们的结果是001,但ip地址是不能写成001, 002的
>>> re.search(r"(([01]\d\d|2[0-4]\d|25[0-5]\d)\.){3}([01]\d\d|2[0-4]\d|25[0-5]\d)", "192.168.100.1")>>>
>>> re.search(r"(([01]\d\d|2[0-4]\d|25[0-5]\d)\.){3}([01]\d\d|2[0-4]\d|25[0-5]\d)", "192.168.100.001")<_sre.SRE_Match object; span=(0, 15), match='192.168.100.001'>
>>>
可以通过{0,1}来优化,表示前面的字符重复0-1次,也可以用 ?
>>> re.search(r"(([01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5]\d)\.){3}([01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5]\d)", "192.168.100.1")<_sre.SRE_Match object; span=(0, 13), match='192.168.100.1'>
>>> re.search(r"(([01]?\d?\d|2[0-4]\d|25[0-5]\d)\.){3}([01]?\d?\d|2[0-4]\d|25[0-5]\d)", "192.168.100.1")<_sre.SRE_Match object; span=(0, 13), match='192.168.100.1'>
这样,当数字只有个位时,百位的0匹配0次,十位的0匹配0次,只留下个位数字[0-9]