非捕获,前瞻与后顾(转)

总揽


前瞻:lookahead
后顾: lookbehind
在perl风格的正则表达式中
非捕获的语法是:(?:)
前瞻的语法是:(?=)
后顾的语法是:(?<=) 他们三者的共同点就是与普通子模式的()不同,不会捕获这些特殊模式内的匹配一下的例子只是为了说明功能编写,并非是实际使用中比较好的方法。 [Edit 非捕获]

非捕获


先拿非捕获来说:
非捕获表达式
/(?:music) is the key/
正常的捕获子模式
/(music) is the key/
都匹配
music is the key
在选择后引用\1的时候,(?:)由于是非捕获组,所以\1无值。()是正常的捕获子模式,所以\1就是music
[Edit 前瞻和非捕获]

前瞻和非捕获


匹配字符串
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/
在匹配到'之后,前瞻开始预测下面的字符,如果符合表达式,那么就继续从这个单引号之后开始匹配。并不占据实际的字符位置。
[Edit 后顾与前瞻]

后顾与前瞻


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

负前瞻与负后顾


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


[Edit 前瞻与后顾用在替换]

前瞻与后顾用在替换


下面讲一个实际的例子
设想有个目录
/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
[Edit 前瞻与后顾用在拆分]

前瞻与后顾用在拆分


这是改自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()


[Edit 一个实际的例子]

一个实际的例子


问题来自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);
?>

这是用前瞻进行拆分的实际例子 Config........0.00126791000366 SECs
Instantiate..0.01136302948 SECs
Render......1.82671999931 SECs
This wiki is under GPL and the latest version can be found here.

posted on 2008-08-26 15:08  y轴  阅读(273)  评论(0编辑  收藏  举报

导航