php实用指南2.0 节选-正则举例和解题思路

第十四章 正则实战和常用正则整理
本章将会讲解一些正则解题的技巧和常用正则,部分实例来自网络,不能保证其效率最佳。
例一:验证密码是否符合规则。要求如下:
只能由小写字母、数字和横线(-)组成;2.开头和结尾不允许是横线;3.不允许全部是数字;4.不允许有连续(超过一个)的横线。
下面我们一一解析:
1.只能由小写字母、数字和横线(-)组成这一条很好办,用字符组『[-a-z0-9]』即可解决,注意我们没有用字符组『\w』,因为一般来说『\w』等价于『[a-z0-9_]』,下划线_也可以匹配;在使用正则表达式时准确限定范围、避免错误匹配,是需要谨记的规矩;

2.开头和结尾不容许是横线这也很好办,我们知道,在正则表达式中,字符串的开头位置用『^』表示,结束位置用『$』表示(关于『\A』和『\Z』的情况暂不讨论,因为密码字符串中不可能出现换行符),这两个锚点(anchor)只匹配位置,不匹配任何字符;开头不容许出现横线,也就是说,从开头位置向后,不容许出现横线字符,我们 可以用否定顺序环视(negative lookahead)功能解决。在本例中,它写作『(?!-)』,其中的『(?!…)』是否定顺序环视的标志符,整个结构表示在当前位置之后(也就是右边一位),不容许出现横线字符,把它和表示字符串开头的『^』连在一起,得到『^(?!-)』,就表示“从字符串的开始位置,向右边看,不容许马上出现横线”;类似的,我们在表达式的末尾使用否定逆序环视,正则表达式『(?<!-)$』就表示“从字符串的末尾位置,向左边看,不容许马上 出现横线”;
3.不容许全部是数字这个要求得动点脑筋,有人一看到“不容许全部是数字”,就想到否定型字符组『[^0-9]*』,这其实是不对的。我们仔细想想,“不容许全部是数字”就是 “必须出现至少一个非数字字符”,而第一条要求字符只能是小写字母、数字和横线,那么这个“非数字字符”只能是小写字母,或者横线。这样一来我们就知道 了,在这个正则表达式中,必须出现一个『[-a-z]』匹配的字符;
4.不容许有连续(超过一个)的横线这种“不容许出现某种连续字符”的情况,是正则表达式中最难处理的地方,因为常见的表示“不容许”的功能,就是排除型字符组『[^…]』,于是,遇到“不容许出现两个连续横线”的情况,许多人就想当然地写下『[^--]』,但这其实大错特错——我们需要谨记,字符组的作用只限于单个字符,所以 『[^--]』的意思是“在这个位置,不能匹配横线”。那么要怎么办呢?一般来说有两个办法,我们可以规定,在一个横线字符匹配之后,不容许继续出现横线,还是应用上面说过的否定顺序环视,『-(?!-)』,就保证了匹配了一个横线之后,不容许继续出现横线,如果在每一个可能匹配横线的地方都加上这个限定,“不容许有连续(超过一个)横线”的要求也就满足了;或者我们也可以在整个正则表达式的最开头,使用否定顺序环视『^(?![-a-z0-9]*–)』,因为表达式『[-a-z0-9]*–』会“尽力寻找可能的匹配”,对它加以否定,就保证了整个字符串中绝对不容许出现两个连续的横线。在这个例子中,我们观察第一条要求对应的表达式,发现横线一般是与小写字母和数字同时出现在一个字符组『[-a-z0-9]』中,如果采取上述第一种办法,因为字符组中只能出现对单个字符的规定(而无法使用类似环视之类的结构),『[-(?!-)a-z0-9]』的意思完全不对,所以整个字符组就要改成括号,以多选结构表示为『(-(?!-)|[a-z0-9])』,显得很累赘,所以优选第二种方法。
   好了,四条要求已经分别解决完毕,现在我们把它们组合起来。
首先,是开头的『^(?!-)』,这就表示“开头不容许出现横线”,在结尾用『(?<!-)$』,表示“结尾不容许出现横线”;其次,之中的内容都只可能是小写字母、数字和横线,所以用字符组『[-a-z0-9]』,因为长度不确定,所以使用量词『*』,变成『[-a-z0-9]*』;再次,整个正则表达式中必须出现一个非数字字符,也就是必须让『[-a-z]』匹配一个字符,因为这个非数字字符出现的位置不确定,我们不妨把上面的表达 式『[-a-z0-9]*』“切开”,把『[-a-z]』塞进去,得到『[-a-z0-9]*[-a-z][-a-z0-9]*』,这样就保证了“在所有由小写字母、数字和横线构成的字符串中,至少出现了一个非数字字符”;最后,不容许出现两个连续的横线,我们的解决办法是在字符组的最开始位置,添加一个否定顺序环视,也就是『(?![-a-z0-9]*–)』,我们把它与之前的『^(?!-)』合并起来,得到『^(?!(-|[-a-z0-9]*–))』。
所以,整个正则表达式就是这样:
^(?!(-|[-a-z0-9]*--))[-a-z0-9]*[-a-z][-a-z0-9]*(?<!-)$
试着理解:^(?!(-|.*–|.*-$))(?=(.*[-a-z]))[-a-z0-9]*$

例二:处于seo的考虑,要求批量给html字符串中a标签中不包含title属性的标签添加title,而且,其title内容为<a href…>到</a>之间的文本。
$str = preg_replace('%<a((?:(?!title="[^"]+?")[\s\S])+?)>(?:(?<!</a>)[\s\S])+?</a>%im','<a title="\\2" \\1>\\2</a>',$str);
print_r($str);

例三:URLRewrite.为了有利于SEO,把网址呢进行伪静态化处理也是一个很流行的做法。以我本地的一个程序为例。
我要实现list.php?Mode=A这样的一个列表页面重写为list-A.html这样的伪静态,则可以这么做:
配置apache。在httpd.conf里把#LoadModule rewrite_module modules/mod_rewrite.so这一行前面的#去掉,启用rewrite规则。
在对应的<Directory "E:/dev/www/php">配置项下设置AllowOverride All。
在网站目录E:/dev/www/php/new 目录下新建.htaccess文件,输入如下内容:
RewriteEngine on
RewriteRule index.html index.php
RewriteRule list-([A-Z]+)\.html$ list.php?mode=$1
好了,重启apache。现在访问http://127.1/index.html以及http://127.1/list-A.html看看效果吧。关键就在第三句,如果你看了我前面的讲解,应该很快就能悟透了。我在这里只简单地提及一下Rewrite,详细的应用会在服务器篇单独开讲。

例四:MYSQL中使用正则。在mysql中查找表的property字段不含数字的行
mysql> select * from tc0_log where content NOT REGEXP "[[:digit:]]";
尽管这个需求很BT,但是你也可以看到正则有时确实很有用,也很常见。Mysql里正则不支持反向引用,但是可以用replace函数实现部分效果。

例五:递归匹配嵌套括号
$string = "some text (a(b(c)d)e) more text";if(preg_match("/\(([^()]+|(?R))*\)/",$string,$matches)){    echo "<pre>"; print_r($matches); echo "</pre>";}
输出:Array(    [0] => (a(b(c)d)e)    [1] => e    )
上面的正则表达式中的关键点是(?R). (?R)的作用就是递归地替换它所在的整条正则表达式. 在每次迭代时, PHP 语法分析器都会将(?R)替换为”\(([^()]+|(?R))*\)“.
现在来细看一下"/\(([^()]+|(?R))*\)/"是怎样匹配"(a(b(c)d)e)"的:
"(c)"这部分被正则式 "\(([^()]+)*\)" 匹配. 请注意, (c) 其实就相当于整个递归的一个缩影, 麻雀虽小五脏俱全, 因此它用到了整个正则表达式.换言之, 下一步中的(c), 可以使用(?R) 来匹配.
(b(c)d)的匹配过程为: "\("匹配"(";"[^()]+"匹配"b";(?R)匹配"(c)";"[^()]+"匹配"d";"\)"匹配")".
    根据上面的匹配原理, 不难理解为什么数组的第2个元素$matches[1]与'e'等价. 子串'e'是在最后一次匹配迭代中被捕获. 匹配过程中, 只有最后一次的捕获结果才会保存到数组中.只需要捕获 $matches[0],则可以把捕获括号()改为非捕获捕获括号(?:)。或者用固化分组:\((?>[^()]+|(?R))*\)

例六:单词转大写
    注意:单词转大写,只是单词的首字母大写。当然了,php有一个ucwords函数可以实现。我们现在用正则实现一个,来学习回调。
    匹配结果中的特定内容有时可能会需要某种特别的修改。要应用多重而复杂的修改,正则表达式的回调就有了用武之地。回调是用于函数preg_replace_callback中的动态修改字串的方式。你可以为preg_replace_callback指定某个函数为参数,此函数能接收匹配结果数组为参数,并将数组修改后返回,作为替换的结果。
    $str = 'i like you do like this .';
    function upper_case( $matches ) {
    return strtoupper( $matches[0] );
    }
    $str=preg_replace_callback( '/\b\w/', 'upper_case', $str);
    echo $str;
    当然,这里用函数最好,不过此处仅为举例说明回调的使用。
   
   上面的几个例子讲解涉及了大部分正则应用的场景,如果你用点心,还会发现正则更大的应用场景。最后,给出一些常见的正则实现:
Utf-8:[\x01-\x7f]|[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}
匹配utf-8编码格式下的中文:[\x80-\xff]{3}或者[\x{4e00}-\x{9fa5}]/u
GB2312 汉字:[\xb0-\xf7][\xa0-\xfe] GB2312全角标点及全角字母:\xa3[\xa1-\xfe]
匹配双字节字符(包括汉字在内):[^x00-xff]
匹配中文 [^\x00-\x7f]\+(注:中文匹配有时候会存在匹配不完整或无匹配的问题,如果一种方式视线不好,那就换另一种试试。)
注意:正则不支持[chr(128)-chr(191)}这样的语法。
数字格式化成用“,”的货币格式:(?<=\d)(?<!\.\d*)(?=(?:\d{3})+(?:\.\d+|$))
    哦,由于笔者太困了,也不赞成伸手主义,也讲了这么多了,其它就靠读者的收集和领悟了。本章和上一章足足花了作者11年元旦假期一个通宵,十多个小时马不停滴的整理,消化与写作。由于时间仓卒,加上篇幅有限,笔者水平也有限,这几章难免有纰漏和错误,欢迎指出。另外一些正则中高级的概念你可以暂时不去理会,我相信只要你认真学完这两章,加上自己的领悟和google,完全可以应付85%以上的正则需求了。
相关资源:
http://blog.csdn.net/lxcnn
http://www.cnblogs.com/deerchao/archive/2006/08/24/zhengzhe30fengzhongjiaocheng.html
http://www.codeproject.com/KB/dotnet/regextutorial.aspx
http://www.cnxct.com
视频教程:
http://www.boobooke.com/v/bbk3748/【49,50,51,52】共五集
http://iregex.org
http://blog.csdn.net/slavik/archive/2006/09/18/1240386.aspx
余老师即将面世的《正则表达式傻瓜书》这本书很值得期待。

仅有一条评论 »

  1. 扬

    不错,正好解决了我之前正则迷糊的地方 3Q

白菜的弟弟的同学的老师的儿子的妈妈养的小狗的表弟的主人的朋友说看帖不回会被鄙视de

添加新评论 »

【f(x,y)=(y^2-4y)(x^2-6x)的极值(请填入答案,答案见本表单title)】