[转]正则表达式之-非捕获,前瞻与后顾

总揽
前瞻:lookahead
后顾: lookbehind
在perl风格的正则表达式中
非捕获的语法是:(?:)
前瞻的语法是:(?=)
后顾的语法是:(?<=) 他们三者的共同点就是与普通子模式的()不同,不会捕获这些特殊模式内的匹配一下的例子只是为了说明功能编写,并非是实际使用中比较好的方法。
非捕获
先拿非捕获来说:
非捕获表达式
/(?:music) is the key/
正常的捕获子模式
/(music) is the key/
都匹配
music is the key
在选择后引用\1的时候,(?:)由于是非捕获组,所以\1无值。()是正常的捕获子模式,所以\1就是music

前瞻和非捕获
匹配字符串
J'taime
或者
J'laime
非捕获的表达式:
/J'(?:[tl]aime)/
和前瞻(lookahead)
/J'(?=[tl]aime)/
在这里,非捕获和前瞻都能匹配J'taime或者J'laime,并且后引用的\1都是无值。
那么它们有什么区别呢
如果你用php的preg_match
看看它对第三个参数的返回
实际上
非捕获(?:)匹配了整个的
J'taime
而前瞻(?=)只匹配了
J'
这个怎么理解呢?
再看下面的例子
同样还是匹配
J'taime
非捕获用
/J'(?:taime)taime/
前瞻用
/J'(?=taime)taime/
结果,非捕获(?:)无法匹配,因为它实际要匹配的是
J'taimetaime
而前瞻则可以正确匹配,并且匹配的结果就是J'taime
这就是非捕获和前瞻的区别
非捕获尽管不捕获字符,但是它在匹配中还是占了实际的字符的位置的,也就是说匹配了(?:)内的内容以后,便开始从这个匹配之后再继续检查
就像匹配了:
taime
之后,便从这个taime往后开始检查
而前瞻(lookahead) (?=)的内容只是提供了一种预测的功能,表达式
/J'(?=taime)taime/
在匹配到'之后,前瞻开始预测下面的字符,如果符合表达式,那么就继续从这个单引号之后开始匹配。并不占据实际的字符位置。

后顾与前瞻
后顾(lookbehind) (?<=)与前瞻(lookahead)一样,提供的一种预测前面(behind,说的是“后面”,所谓的“后面”,其实就是前面的字符,我们阅读顺序是从左往右,越后就是越左)字符的匹配,表达式 /(?<=J')taime/
同样匹配
J'taime
但是匹配的内容是taime,taime前面的内容就是后顾要预测的内容。

负前瞻与负后顾
常规的前瞻与后顾分别是(?=)与(?<=) 负前瞻和负后顾分别是(?!)(?的内容必须不如此
负前瞻与负后顾实用中我遇到的还比较少,请看下面的一些例子。



前瞻与后顾用在替换
下面讲一个实际的例子
设想有个目录
/home/a/b
a目录与b目录有一些PHP文件,这些文件都用相对路径引用了
/home/a/comm/
/home/a/inc
下面的一些文件
如果你把a目录的一批文件移动到b目录
那么原a目录的文件引用的仍然是当前目录的
./comm/
./inc

comm/
inc/
原b目录的文件则是正确的
../comm/
../inc/
我们需要将那些非../开头的目录的引用文件改成../开头的
比如c.php
require './comm/global.php';
require_once("comm/user.php");
require_once 'comm/conn.php';
require_once "inc/inc.inc";
........
<img src="./top.gif" />
我们要替换require引入的那些文件,其他的图片等都是正确的。
我们先要确立要替换那一行的代码的唯一性,按照普通的方式,就是匹配
/(require.*['"])(?![.]{2})(.*)(['"])/
替换成
\1../\2\3
这里就用到了负前瞻,表示引号后面的第一个内容,必须不是两个"..",如果是,则整个匹配失效。所以就可以跳过包含正确路径的文件,捕捉错误路径的文件
但是这种方式比较傻,其实后引用的\1,\3完全可以不要,我们要改造的只有\2
但是如何在确立代码的唯一性的前提下,又不匹配那些用来确定唯一性的字符呢?
我们利用前瞻后顾提出如下思路:
1.预测前面的内容,但不捕获(lookbehind)
2.中间要改造的path
3.预测后面的内容,但不捕获(lookahead)
我们把\1,\3变成预测,修改表达式如下:
/               #模式开始
(?<=            #后顾开始预测前面的内容
require.*['"] #要预测的内容
)               #后顾预测结束
(?![.]{2})      #要跳过的正确路径
(.*)            #子模式真正要匹配捕捉并替换的内容path
(?=['"])        #前瞻用来预测下面的必须是引号
/x              #x的作用就是消除注释和空白
这里匹配require那段我们用到了后顾,但是为什么不用前瞻?
前瞻(?=)虽然也预测
require....."
这样的内容,但是它是一种超前预测,也就是预测下面的内容,
所以在确定下面的内容是正确的之后,就开始从require的开头继续匹配
(预测结束从这里开始匹配)require....."
但是后顾预测的是前面的内容,也就是说
require......"(预测结束从这里开始匹配,也就是路径开始的部分了)
由此看来,后顾才是我们想要的方式
但是很不幸,上面的表达式在PHP中无法起作用
也许是PHP版本的问题
我的后顾内部:
不支持? * +这样的量词
不支持子模式()的后引用\n

前瞻与后顾用在拆分
这是改自PHP Programming的例子
如下字符
saisai from marssurfchen from earth
等等,我们需要拆分成
1.人名
2.介词+地点
普通的explode,
以from为分隔符的话,拆分后就变成
array('saisai','mars');
array('surfchen','earth')
把from给丢了
此时如果用前瞻与后顾
/(?=from)/
告诉程序,如果下一个就是from那么就从这开始分吧。(这其实就是个空字符,from前面的空字符,从它断开人名与from)
结果就是
array("saisai","from mars");
array("surfchen","from earth");
如果用后顾,告诉程序 ,如果上面是from,那么就开始分吧
/(?<=from)/
结果就是
array("saisai from","mars");
array("surfchen from","earth");
至此就讲这么多
相关PHP函数就是preg_split()



一个实际的例子
问题来自http://www.phpx.com/viewarticle.php?id=134675

$str="id,name,pass,to_char(UNIT_DATE,'YYYY-MM-DD HH24:MI:SS'),level";
上面这个字符串怎么分割开,得到数组:
id
name
pass
to_char(UNIT_DATE,'YYYY-MM-DD HH24:MI:SS')
level

解:
<?php
$str="id,name,pass,to_char(UNIT_DATE,'YYYY-MM-DD HH24:MI:SS'),level";
$matches=preg_split('#(?:,(?=\w+\(.*\))|,(?=\w+))#',$str);
print_r($matches);
?>
这是用前瞻进行拆分的实际例子

 

来源:http://www.wmdxbbs.com/viewthread.php?tid=3496

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值