怎样用正则表达式匹配IPV4地址

写论文写累了,忽然想起以前面试时的一道题:怎样用正则表达式匹配IPV4地址?在网上找到两篇很好的英文文章,把相关内容翻译综合一下,与诸君共享。
原文链接:
http://blogs.msdn.com/b/oldnewthing/archive/2006/05/22/603788.aspx
http://answers.oreilly.com/topic/318-how-to-match-ipv4-addresses-with-regular-expressions/


    写一个正则表达式匹配IPV4地址说容易也容易,说难也难,取决于需要的准确度。简单起见,这里只考虑点分十进制的IPV4地址。
    最简单的方式是把IPV4地址看做四段十进制数字串,由三个点号隔开,可以采用如下写法:
^\d+\.\d+\.\d+\.\d+$
就其本身而言没有问题,但会错误地匹配"448.90210.0.65535"这样的字符串,而一个恰当的IPV4表示法中每个域中的值不应大于255。但写一个匹配0到255的整数的正则表达式并不容易,因为正则表达式不理解算术,而是单纯基于文本。考虑下面一些情况:
单独的一个数字(表示0-9)
一个非零数字后紧跟着另外一个数字(表示10-99)
"1"后面跟着两个数字(表示100-199)
"2"后面跟着一个"0"到"4"间的数字,后面又跟着一个数字(表示200-249)
"25"后面跟着一个"0"到"5"间的数字(表示250-255)
    根据上面的分析,来试着写匹配0-255的正则表达式:
^\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]$
将前两项合并,上式变成:
^[1-9]?\d|1\d\d|2[0-4]\d|25[0-5]$


    然后,将上面这个式子重复4次,中间以点号隔开即可,如下所示:
^([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$
    好了,我们完成了点分十进制的简单描述,同时写了一个基本不可读的怪异的正则表达式。想象你正在维护一个程序时碰到了这个式子,你要花多长时间才能看懂它表达的意思?
    可能这个式子还是不太对,因为有些解析器支持在每个十进制数前的前导零(例如,127.0.0.001和127.0.0.1是相通的,但另外一些解析器将前导零作为八进制的前缀,orz)。好,现在我们考虑前导零,上面的正则表达式变成:
^0*([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.0*([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.0*([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.0*([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$
    这就是我对正则表达式既爱又恨的原因。简单模式有许多种表示方式,复杂模式的表示方式更是多得可怕。啊哈,你可能看到了,浪费这么多时间写一个正则表达式真是个错误。我们忘了去搞清楚:实际问题到底是什么?搞懂了这个可以解决一半的问题。现在问问自己:“我有一个字符串,想检查它是否是一个点分十进制的IPV4地址,我得写一个正则表达式!嘿,谁能帮我写一下?”
    真正的问题不是“我怎么写一个识别点分十进制IPV4地址的正则表达式”,而是“我怎么识别一个点分十进制IPV4地址”。头脑里思考这个更大的目标,你会发现逼你自己写一个正则表达式纯粹是自寻烦恼。
    来看看下面的函数:
function isDottedIPv4(s) { var match = s.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/); return match != null && match[1] <= 255 && match[2] <= 255 && match[3] <= 255 && match[4] <= 255; } WScript.StdOut.WriteLine(isDottedIPv4("127.0.0.001")); WScript.StdOut.WriteLine(isDottedIPv4("448.90210.0.65535")); WScript.StdOut.WriteLine(isDottedIPv4("microsoft.com"));
    问题是不是已经搞定了?怎么样?想不想试试解析电子邮件的地址?
    别让正则表达式做它们不适合的事情。如果你想匹配一个简单的模式,就匹配一个简单的模式。正如评论家Maurits所说:“诀窍不是花时间去造锤子和螺丝刀,直接用锤子和螺丝刀就OK了”。


    恩,写得不错,都上升到方法学的角度上来了,不过我很赞同,解决问题永远是第一位的,死钻技术牛角尖并不可取,这是面对问题时我们应采取的态度。但为了研究技术,深入考究也是必须的!下面来看看另一篇里的写法。
    检查IP地址的简单正则表达式:
^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$
    来一个更准确的:
^(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$
    用于从长文本中提取IP地址的正则表达式:
\b(?:[0-9]{1,3}\.){3}[0-9]{1-3}\b
\b(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b
    将IP地址的四个部分完全展开,有:
^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$


^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.


Perl
if ($subject =~ m/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/) {     $ip = $1 << 24 | $2 << 16 | $3 << 8 | $4; }
    这里有两个版本,一个是简单的,另一个是准确的。简单版利用[0-9]{3}表示IP地址的每个域,实际上支持0-999之间的数字,而非0-255。当你的输入只包含有效的IP地址时,简单版会更有效,这时你只要把IP地址和其他内容分开就行了。准确版用25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?来匹配IP地址的每个域,准确地匹配了0-255间的数字。(这个式子具体什么意思不解释了,第一篇有解释)
    如果想检查一个串是否完全是个IP地址,可以再首尾分别加上^和$符号。如果想在长文本中查找一个IP地址,可以用字边界符\b。
    使用(?:number\.){3}number的形式,IP地址的前三个数字重复三次,且为非获取性匹配,最后一部分匹配IP地址的最后一个数字。使用非获取性匹配并重复三次可以使我们的正则表达式更短更有效。
    要将IP地址的文本表示转换为整数表示,我们需要分别获取IP地址的四个数字。上面最后的两个正则表达式做的就是这样的工作,其不是重复三次,而是完全展开,这也是唯一的办法。
    一旦获取了数字,将其转换为32位的数字就很简单了。在Perl中,$1,$2,$3,$4保存对应的文本,当我们对其使用左移运算符<<时,这些变量将自动转变为整型数字。在其它语言中,可能需要在对数字进行移位之前调用String.toInteger()或其它类似的函数,然后将它们用位或合并起来。


    OK,写完了,对你有没有一点启发呢?

你可能感兴趣的:(c++)