正则表达式匹配规则
2016-10-24 18:41
232 查看
前面已经讲过
例如
注意:[]匹配单个字符,尽管看起来[]里有好多字符。
也可以指定字符范围,例如
字符组很简单,但是一定要弄清楚字符组中什么时候需要转义。
通俗地讲,转义就是防止特殊字符被解析,或者说用某个符号表示另一个特殊符号。例如:
在JavaScript或者PHP中都接触过转义的概念。例如,JavaScript中要弹出一个对话框,对话框中需要分成两行显示,用HTML的
在PHP里使用反斜杠(\)表示转义,\Q和\E也可以在模式中忽略正则表达式元字符,比如:
以上表达式先匹配一个或多个数字,紧接着一个点号,然后一个,再然后一个点号,最后是字符串末尾。也就是说,\Q和\E之间的元字符都会作为普通字符用来匹配。
正则表达式是不是遇到这些特殊字符就该转义呢?答案显然是否定的。转义只有在一定条件下,比如可能引起歧义或者被误解析的情况下才需要。有些情况并不需要转义这些“特殊”字符,并且在时转义也是无效的。这需要不断尝试并积累经验。看一个例子:
运行结果为
在字符组中匹配”a”,”b”,”y”和”}”中任意一个,由于
但是实际上,这个转义是多余的。虽然”}”是元字符,具有特殊意义,但是
在字符组中,”}”却无法发挥意义,不会引起歧义,所以不需要转义。在这里
既然转义符
如果确实要把
运行结果为:
前面提到,不是所有出现特殊字符的地方都要转义。例如,以下正则表达式可以匹配“cat”、“c?t”、“c)t”等字符:
其中
字符组里可以使用转义吗?可以,例如
“(“和”)”也是元字符,所以在这里需要转义。这个表达式可以匹配几种格式的电话号码,例如(010)11223344、012-11223344或01211223344等。首先是转义符”(“,表示出现0次或1次(?),然后是一个0,后面跟着两个数字(\d{2}),然后是”)”、”-“或空格中的一个,出现出现0次或1次(?),最后是8个数字(\d{8})。
反义有个比较明显的特征,就是和一些已知元字符相反,并且为大写形式。比如”\d”表示数字,而”\D”就是表示非数字。例如
1)匹配不包含空白符的字符串
2)用尖括号括起来,以a开头的字符串
比如,要匹配字符串
提示:”^”在这里是”非”的意思,不要和表示开头的”^”混淆。那怎么区分呢?很简单,表示开始位置的”^”只能用在正则表达式的最前端,而表示取反的”^”只用在字符组中,即只在中括号中出现。记住这一点,就不会搞混了。
日常工作中反义用得不多,因为扩大了范围。例如程序里的变量,第一个字符不允许是数字,一般使用
其中括号里的表达式将视作一个整体(后面会讲到分组的概念),“|”表示分支,即可能存在的多种情况,可以匹配多个字符。分支的功能更强大,字符组方式只能对单个字符“分支”,而分支可以是多个字符以及更复杂的表达式。但对于单字符的情况,字符组的效率更高。也就是说,能使用字符组就不用分支。
注:上述的
正则表达式分支条件指有几种规则,无论满足其中哪一种规则都能匹配,具体方法是用“|”把不同规则分隔开,例如:
这个表达式能匹配以”-“分隔的电话号码:一种是3位区号,8位本地号码(如010-11223344),一种是4位区号,7位本地号码(如0312-1223454)。匹配3位区号的电话号码表达式如下:
其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用”-“或空格隔开,也可以没有间隔。
例如,美国邮编规则是5位数字,或者用连字号间隔的9位数字。匹配表达式如下所示:
另外,使用分支条件时,要注意各个条件的顺序。如果改成如下形式,就只匹配5位邮编以及9位邮编的前5位。
注意:匹配分支条件时,将从左到右测试每个条件,如果满足某个分支,就不会再考虑其他条件了
例如:简单的IP地址匹配表达式如下:
要理解上述表达式,应按照如下顺序分析:
1)匹配1-3位的数字:
2)匹配3位数字机上1个英文句号(分组),重复3次(最后加上一个1-3位的数字):
IP地址中每个数字都不能大于255,所以严格的说这个正则是有问题的。如果使用算术比较,或许能简单地解决这个问题,但是正则表达式中没有提供关于数学的任何功能,所以只能使用冗长的分组、选择、字符类来描述一个正确的IP地址,如下所示:
解析:
上述就能表示0-255,因为
默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组,其组号为1,第二个为2,以此类推;分组0对应整个正则表达式。
也可以自己指定子表达式的组名,语法如下:
把尖括号换成单引号也行,如下所示:
这样就把\w+组名指定为Word。
提示:组号分配远没有这么简单。组号分配过程是要从左向右扫描两遍:第一遍只给未命名组分配,第二遍只给命名组分配。因此,所有命名组的组号都大于未命名的组号。可以使用语法
以上表达式可以匹配重复的单词,例如go go或者kitty kitty。首先这个表达式是一个单词,也就是单词开始处和结束处之间大于一个的字母或数字,即”\b(\w+)\b”,这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词),即\1,这样就相当于把匹配的重复一次。
要反向引用分组捕获的内容,可以使用
例如,要捕获字符串
将返回
表达式“\1,\2,……,\9”是对前面已捕获子内容的编号,可以作为对这些编组的“指针”引用。在此例中,第一个匹配的引号就由1代表。可以这么写成:
如果使用命名捕获组,可以写成:
看PHP使用反向引用的例子。
在很多论坛中都会看到UBB标签代码。UBB标签最早的设计是用来在论坛和留言本里代替HTML,实现一些简单的HTML效果,同时防止滥用HTML出现安全问题。例如,HTML中粗体的标签是:
或者
而UBB标签则是:
UBB标签以其更好的安全性,目前已经成为论坛发帖的代码标准,只不过不同论坛产品的叫法不一样而已。
最终,UBB标签还是要解析成HTML代码,才能让浏览器认识。这个过程是怎样实现的呢?下面以URL标签为例解释。
例如,UBB标签
运行结果为:
这里再给出一个表达式实现同样的效果:
比如,匹配以“ing”结尾的单词前面部分(除了“ing”以外的部分):
比如,以re开头的单词的后半部分(除了re以外的部分):
运行结果为:
Array ( [0] => Array ( [0] => ading ) )
下面这个例子,同时使用上面这两种断言,匹配以空白符间隔的数字(再次强调,不包括这些空白符):
前面提到过反义,用来查找不是某个字符或不在某个字符类里的字符。如果只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果想查找这样的单词——出现字母q,但是q后面跟的不是字母u。可以尝试这样:
以上表达式匹配包含后面不是字母u的字母q的单词。但是如果多做几次测试就会发现,如果q出现在单词的结尾,例如Iraq、Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,如果q是单词的最后一个字符,后面的“[^u]”将会匹配q后面的单词分隔符(可能是空格、句号或其他),后面的“\w*\b”将会匹配下一个单词,于是以上表达式就能匹配整个Iraq fighting。
逆序肯定环视能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,解决这个问题如下所示:
运行结果为:
Array ( [0] => Array ( [0] => Iraq fighting ) )
运行结果为:
Array ( [0] => Array ( [0] => Iraq ) )
1)匹配3位数字,而且这3位数字的后面不能是数字:
2)匹配不包含连续字符串abc的单词:
如果匹配的单词是c开头、t结尾,中间有一个字符,但不能是u(也就是说,整个单词不能是cut),直接用“c[^u]t”就可以了,若中间的字符不能是a或u(也就是说,整个单词不能是cat或cut),则表达式改为“c[^au]t”。
这个表达式能匹配的只是cot之类的单词,因为中间的排除型字符组“[^au]”必须匹配一个字符。可是,如果还想匹配chart、conduct和court怎么办?最简单的想法是:去掉排除型字符组的长度限制,改成“c[^au]+t”。
不幸的是,这样行不通,因为这个表达式的意思是:c和t之间由多于一个“除a或u之外的字符”构成,而chart、conduct和court都包含a或u。
我们发现,其实要否定的是“单个出现的a或u”,而不仅仅是“出现的a或u”,所以才出现这样的问题。要解决这个问题,就应当把意思准确表达出来,变成“在结尾的t之前,不允许只出现一个a或u”。想到这一步,就可以用顺序否定环视(?!……)来解决。表示在这个位置向右,不允许出现子表达式能够匹配的文本,把子表达式规定为“[au]t\b”(最后的“\b”很重要,它出现在t之后,保证t是单词的结尾字母)。有了限制,匹配a和t之间文本的表达式就随意很多,可以用匹配单词字符的简记法“\w”表示,于是整个表达式变成:
注意:这里出现的并不是排除型字符组
进一步思考,整个匹配文本中都不能出现字符串“cat”,要怎么办呢?这个正则表达式应该是:
即在文本中的任意位置,都不能出现该字符串。
分析以下表达式,匹配不包含属性的简单HTML标签内的内容:
以上表达式最能表现零宽断言的真正用途。(<?(\w+)>)指定前缀为:被尖括号括起来的单词(比如可能是“<b>”),然后是“.*”(任意的字符串)最后是一个后缀(?=<\/\1>)。【注意】后缀里的“\/”,用到了前面提过的字符转义;“\1”则是反向引用,引用的正是捕获的第一组,即前面(\w+)匹配的内容,如果前缀实际上是“<b>”,后缀就是“</b>”。整个表达式匹配的是“<b>”和“</b>”之间的内容(再次提醒,不包括前缀和后缀本身)。
总体而言,环视相当于对“所在位置”附加一个条件,难点就在于找到这个“位置”。这一点解决了,环视就没有什么秘密可言了。
如果用来搜索“aabab”,它会匹配整个字符串“aabab”。这就是贪婪匹配。
有时,需要匹配尽可能少的字符,也就是懒惰匹配。前面给出的限定符都可以转化为懒惰匹配模式,只要在后面加上一个问号。例如“.*?”就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。例如,匹配以a开始、以b结束的最短字符串,正则表达式如下:
把上述表达式应用于aabab,如果只考虑“.*?”这个表达式,最先会匹配到aab(1~3字符)和ab(第2~3个字符)这两组字符。
为什么第一个匹配是aab(第1~3个字符)而不是ab(第2~3个字符)?简单地说,正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高优先权。
常用懒惰限定符如表所示。
懒惰模式匹配原理简单来说,是在匹配和不匹配都可以的情况下,优先不匹配,记录备选状态,并将匹配控制交给正则表达式的下一个匹配字符。当后面的匹配失败时,回溯,进行匹配。关于回溯以及正则表达式效率等高级内容,可以查阅《精通正则表达式》一书。
例如:
运行结果为:
如果改为贪婪匹配
运行结果为:
在贪婪模式下,由于匹配表达式是“.*”,即任意字符出现任意次,这个正则表达式会一直匹配[url]后的内容,直到遇到结束条件“[\/”。
实际开发中,涉及贪婪模式与懒惰模式的地方是很多的。在一定情况下,使用懒惰模式可以减少回溯,提高效率。
"*",
"-",
"?"等元字符,它们都有各自的特殊含义。如果想匹配没有预定义元字符的字符集合,或者表达式和已知定义相反,或者存在多种匹配情况,应该怎么办?
字符组
查找数字、字母、空白很简单,因为已经有了对应这些字符集合的元字符,但是如果想匹配没有预定义元字符的字符集合(比如元音字母a、e、i、o、u),方法很简单,只需要在方括号里列出它们。例如
[aeiou]匹配任何一个英文元音字母,
[.?!]匹配标点符号(”.”、”?”、”!”),
c[aou]t匹配”cat”,”cot”,”cut”这三个单词,而”caout”则不匹配。
注意:[]匹配单个字符,尽管看起来[]里有好多字符。
也可以指定字符范围,例如
[0-9]的意义与
\d完全一致:代表以为数字;同理
[a-z0-9A-Z_]完全等价于
\w(如果只考虑英文)。
字符组很简单,但是一定要弄清楚字符组中什么时候需要转义。
转义
如果想查找或匹配元字符本身,比如查找*、?等就出现问题:没办法指定,因为它们会被解释成别的意思。这时就使用
\来取消这些字符的特殊意义。因此,应该使用
\.和
\*。当然,查找
\本身用
\\。这叫做转义。
通俗地讲,转义就是防止特殊字符被解析,或者说用某个符号表示另一个特殊符号。例如:
unibetter\.com匹配
unibetter.com,
C:\\Windows匹配
C:\Windows。
在JavaScript或者PHP中都接触过转义的概念。例如,JavaScript中要弹出一个对话框,对话框中需要分成两行显示,用HTML的
<br>标签或者在源代码里手工换行都不行,应该用
\r\n表示换行并新起一行,如下所示:
alert("警告<br>操作无效");//错误 alert("警告\r\n操作无效");//正确
在PHP里使用反斜杠(\)表示转义,\Q和\E也可以在模式中忽略正则表达式元字符,比如:
\d+\Q.$.\E$
以上表达式先匹配一个或多个数字,紧接着一个点号,然后一个,再然后一个点号,最后是字符串末尾。也就是说,\Q和\E之间的元字符都会作为普通字符用来匹配。
正则表达式是不是遇到这些特殊字符就该转义呢?答案显然是否定的。转义只有在一定条件下,比如可能引起歧义或者被误解析的情况下才需要。有些情况并不需要转义这些“特殊”字符,并且在时转义也是无效的。这需要不断尝试并积累经验。看一个例子:
<?php $reg = "#[aby\}]#"; $str = 'a\bc[]{}'; preg_match_all($reg,$str,$m); var_dump($m);
运行结果为
array(1) { [0]=> array(3) { [0]=> string(1) "a" [1]=> string(1) "b" [2]=> string(1) "}" } }
在字符组中匹配”a”,”b”,”y”和”}”中任意一个,由于
"}"是元字符,具有特殊意义,所以这里进行了转义,使用
"\}"表示
"}"。
但是实际上,这个转义是多余的。虽然”}”是元字符,具有特殊意义,但是
在字符组中,”}”却无法发挥意义,不会引起歧义,所以不需要转义。在这里
"\}"和
"}"是等价的。
既然转义符
"\"是多余的,那么会不会被当作普通字符呢?字符串str里有
"\",但是可以从代码运行结果中看出,
"\"字符并没有被匹配,也就是说正则表达式
“#[aby\}]#”中,虽然
"\"转义符是多余的,但是也并没有被当作普通字符进行匹配。
如果确实要把
"\"当作普通字符匹配,正则表达式需要写成:
<?php $reg = "#[aby\\\}]#"; $str = 'a\bc[]{}'; preg_match_all($reg,$str,$m); var_dump($m);
运行结果为:
array(1) { [0]=> array(4) { [0]=> string(1) "a" [1]=> string(1) "\" [2]=> string(1) "b" [3]=> string(1) "}" } }
前面提到,不是所有出现特殊字符的地方都要转义。例如,以下正则表达式可以匹配“cat”、“c?t”、“c)t”等字符:
c[aou?*)]t
其中
"?"和
"*"等特殊字符都不需要转义。原因很简单,字符组里匹配的是单个字符,这些特殊字符不会引起歧义。
字符组里可以使用转义吗?可以,例如
"c[\d]d"可以匹配“c1d”、“c2d”等。下面是复杂的表达式:
\(?0\d{2}[) -]?\d{8}
“(“和”)”也是元字符,所以在这里需要转义。这个表达式可以匹配几种格式的电话号码,例如(010)11223344、012-11223344或01211223344等。首先是转义符”(“,表示出现0次或1次(?),然后是一个0,后面跟着两个数字(\d{2}),然后是”)”、”-“或空格中的一个,出现出现0次或1次(?),最后是8个数字(\d{8})。
反义
有些时候,查找的字符不属于某个字符类,或者表达式和已知定义相反(比如除了数字以外其他任意字符),这时需要用到反义。常用反义如表所示。反义有个比较明显的特征,就是和一些已知元字符相反,并且为大写形式。比如”\d”表示数字,而”\D”就是表示非数字。例如
1)匹配不包含空白符的字符串
\S+
2)用尖括号括起来,以a开头的字符串
<a[^>]+>
比如,要匹配字符串
"<a href='http://baidu.com'>百度</a>",这个正则表达式的匹配结果就是
"<a href='http://baidu.com'>"。
提示:”^”在这里是”非”的意思,不要和表示开头的”^”混淆。那怎么区分呢?很简单,表示开始位置的”^”只能用在正则表达式的最前端,而表示取反的”^”只用在字符组中,即只在中括号中出现。记住这一点,就不会搞混了。
日常工作中反义用得不多,因为扩大了范围。例如程序里的变量,第一个字符不允许是数字,一般使用
"^[a-zA-Z_]"表示,而不会使用
"\D",因为
"\D"扩大了范围,包括所有非数字的字符,显然,变量命名不仅仅要求第一个字符不是数字,也不能是其他除了26个大小写字母和下画线以外的字符。因此,不要随意使用反义,以免无形中扩大范围,而使自己没有考虑到。
分支
分支就是存在多种可能的匹配情况。例如,匹配“cat”或者“hat”,可以写成[ch]at;要匹配“cat”、“hat”、“fat”、“toat”,很显然不能用字符组匹配的方式。这里表明前面的匹配字符可以是c、h、f或者to,而[]只能匹配单个字符,此时可用分支形式,即:
(c|h|f|to)at
其中括号里的表达式将视作一个整体(后面会讲到分组的概念),“|”表示分支,即可能存在的多种情况,可以匹配多个字符。分支的功能更强大,字符组方式只能对单个字符“分支”,而分支可以是多个字符以及更复杂的表达式。但对于单字符的情况,字符组的效率更高。也就是说,能使用字符组就不用分支。
注:上述的
[ch]at可以写成
(c|h)at。
正则表达式分支条件指有几种规则,无论满足其中哪一种规则都能匹配,具体方法是用“|”把不同规则分隔开,例如:
0\d{2}-\d{8}|0\d{3}-\d{7}
这个表达式能匹配以”-“分隔的电话号码:一种是3位区号,8位本地号码(如010-11223344),一种是4位区号,7位本地号码(如0312-1223454)。匹配3位区号的电话号码表达式如下:
\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}
其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用”-“或空格隔开,也可以没有间隔。
例如,美国邮编规则是5位数字,或者用连字号间隔的9位数字。匹配表达式如下所示:
\d{5}-\d{4}|\d{5}
另外,使用分支条件时,要注意各个条件的顺序。如果改成如下形式,就只匹配5位邮编以及9位邮编的前5位。
\d{5}|\d{5}-\d{4}
注意:匹配分支条件时,将从左到右测试每个条件,如果满足某个分支,就不会再考虑其他条件了
分组
重复单个字符只需要直接在字符后面加上限定符,但如果想重复多个字符又该怎么办呢?可以用小括号指定子表达式,然后规定这个子表达式的重复次数,也可以对子表达式进行其他一些操作。常用分组语法如表所示。例如:简单的IP地址匹配表达式如下:
(\d{1,3}\.){3}\d{1,3}
要理解上述表达式,应按照如下顺序分析:
1)匹配1-3位的数字:
\d{1,3}
2)匹配3位数字机上1个英文句号(分组),重复3次(最后加上一个1-3位的数字):
(\d{1,3}\d){3}
IP地址中每个数字都不能大于255,所以严格的说这个正则是有问题的。如果使用算术比较,或许能简单地解决这个问题,但是正则表达式中没有提供关于数学的任何功能,所以只能使用冗长的分组、选择、字符类来描述一个正确的IP地址,如下所示:
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
解析:
2[0-4]\d|25[0-5]|[01]?\d\d?
上述就能表示0-255,因为
2[0-4]\d表示的为200-249,
25[0-5]表示的是250-255,
[01]?\d\d?表示的为0-199。
默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组,其组号为1,第二个为2,以此类推;分组0对应整个正则表达式。
也可以自己指定子表达式的组名,语法如下:
?<Word>\w+
把尖括号换成单引号也行,如下所示:
?'Word'\w+
这样就把\w+组名指定为Word。
提示:组号分配远没有这么简单。组号分配过程是要从左向右扫描两遍:第一遍只给未命名组分配,第二遍只给命名组分配。因此,所有命名组的组号都大于未命名的组号。可以使用语法
(?:exp)剥夺一个分组对组号分配的参与权。
反向引用
反向引用用于重复搜索前面某个分组匹配的文本。例如,”\1”代表分组1匹配的文本:\b(\w+)\b\s+\1\b
以上表达式可以匹配重复的单词,例如go go或者kitty kitty。首先这个表达式是一个单词,也就是单词开始处和结束处之间大于一个的字母或数字,即”\b(\w+)\b”,这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词),即\1,这样就相当于把匹配的重复一次。
要反向引用分组捕获的内容,可以使用
"\k<Word>",所以上个例子可以写成这样:
\b(?<Word>\w+)\b\s+\k<Word>\b
例如,要捕获字符串
"This is a 'string'"引号内的字符,如果使用一下正则表达式:
(\"|').*?(\"|')
将返回
"This is a '。显然,这并不是我们想要的内容。这个表达式从第一个双引号开始匹配,遇到单引号之后就错误地结束匹配。这是因为表达式里包含”|”,也就是双引号(”)和单引号(’)均可。要修正这个问题可以用反向引用。
表达式“\1,\2,……,\9”是对前面已捕获子内容的编号,可以作为对这些编组的“指针”引用。在此例中,第一个匹配的引号就由1代表。可以这么写成:
("|\').*?\1
如果使用命名捕获组,可以写成:
(?P<quote>"|').*?(?P=quote)
看PHP使用反向引用的例子。
在很多论坛中都会看到UBB标签代码。UBB标签最早的设计是用来在论坛和留言本里代替HTML,实现一些简单的HTML效果,同时防止滥用HTML出现安全问题。例如,HTML中粗体的标签是:
<b>粗体</b>
或者
<strong>粗体</strong>
而UBB标签则是:
粗体
UBB标签以其更好的安全性,目前已经成为论坛发帖的代码标准,只不过不同论坛产品的叫法不一样而已。
最终,UBB标签还是要解析成HTML代码,才能让浏览器认识。这个过程是怎样实现的呢?下面以URL标签为例解释。
例如,UBB标签
1.gif用于插入表情。在解析时,需要把1.gif换成实际路径,并且需要用HTML的IMG标签进行替换,方法如下所示:
<?php $str='1.gif2.gif3.gif'; $s=preg_replace("#\[url\](?<WORD>\d\.gif)\[\/url\]#","<img src=http://www.test.com/upload/$1>",$str); var_dump($s);
运行结果为:
string(126) "<img src=http://www.test.com/upload/1.gif><img src=http://www.test.com/upload/2.gif><img src=http://www.test.com/upload/3.gif>"
这里再给出一个表达式实现同样的效果:
<?php $str='1.gif2.gif3.gif'; $s=preg_replace("#\[url\](.*?)\[\/url\]#","<img src=http://www.test.com/upload/$1>",$str); var_dump($s);
环视
断言用来声明一个应该为真的事实。正则表达式中,只有当断言为真时才会继续进行匹配。断言匹配的是一个事实,而不是内容。本文将介绍四个断言,它们用于查找在某些内容(但并不包括这些内容)之前或之后,也就是一个位置(如\b、^、)应该满足的一定条件(即断言),因此也称为零宽断言。1.顺序肯定环视(?=exp)
零宽度正预测先行断言,又称顺序肯定环视,断言自身出现位置的后面能匹配表达式exp。比如,匹配以“ing”结尾的单词前面部分(除了“ing”以外的部分):
\b\w+(?=ing\b)
2.逆序肯定环视(?<=exp)
零宽度正回顾后发断言,又称逆序肯定环视,断言自身出现位置的前面能匹配表达式exp。比如,以re开头的单词的后半部分(除了re以外的部分):
(?<=\bre)\w+\b
<?php $reg = "#(?<=\bre)\w+\b#"; $str = 'reading a book'; preg_match_all($reg,$str,$m); print_r($m);
运行结果为:
Array ( [0] => Array ( [0] => ading ) )
下面这个例子,同时使用上面这两种断言,匹配以空白符间隔的数字(再次强调,不包括这些空白符):
(?<=\s\d+(?=\s))
前面提到过反义,用来查找不是某个字符或不在某个字符类里的字符。如果只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果想查找这样的单词——出现字母q,但是q后面跟的不是字母u。可以尝试这样:
\b\w*q[^u]\w*\b
以上表达式匹配包含后面不是字母u的字母q的单词。但是如果多做几次测试就会发现,如果q出现在单词的结尾,例如Iraq、Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,如果q是单词的最后一个字符,后面的“[^u]”将会匹配q后面的单词分隔符(可能是空格、句号或其他),后面的“\w*\b”将会匹配下一个单词,于是以上表达式就能匹配整个Iraq fighting。
逆序肯定环视能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,解决这个问题如下所示:
\b\w*q(?!u)\w*\b
<?php $reg = "#\b\w*q[^u]\w*\b#"; $str = 'Iraq fighting'; preg_match_all($reg,$str,$m); print_r($m);
运行结果为:
Array ( [0] => Array ( [0] => Iraq fighting ) )
<?php $reg = "#\b\w*q(?!u)\w*\b#"; $str = 'Iraq fighting'; preg_match_all($reg,$str,$m); print_r($m);
运行结果为:
Array ( [0] => Array ( [0] => Iraq ) )
3.顺序否定环视(?!exp)
零宽度负预测先行断言,又称顺序否定环视,断言此位置的后面不能匹配表达式“exp”。例如:1)匹配3位数字,而且这3位数字的后面不能是数字:
\d{3}(?!\d)
2)匹配不包含连续字符串abc的单词:
\b((?!abc)\w)+\b
如果匹配的单词是c开头、t结尾,中间有一个字符,但不能是u(也就是说,整个单词不能是cut),直接用“c[^u]t”就可以了,若中间的字符不能是a或u(也就是说,整个单词不能是cat或cut),则表达式改为“c[^au]t”。
这个表达式能匹配的只是cot之类的单词,因为中间的排除型字符组“[^au]”必须匹配一个字符。可是,如果还想匹配chart、conduct和court怎么办?最简单的想法是:去掉排除型字符组的长度限制,改成“c[^au]+t”。
不幸的是,这样行不通,因为这个表达式的意思是:c和t之间由多于一个“除a或u之外的字符”构成,而chart、conduct和court都包含a或u。
我们发现,其实要否定的是“单个出现的a或u”,而不仅仅是“出现的a或u”,所以才出现这样的问题。要解决这个问题,就应当把意思准确表达出来,变成“在结尾的t之前,不允许只出现一个a或u”。想到这一步,就可以用顺序否定环视(?!……)来解决。表示在这个位置向右,不允许出现子表达式能够匹配的文本,把子表达式规定为“[au]t\b”(最后的“\b”很重要,它出现在t之后,保证t是单词的结尾字母)。有了限制,匹配a和t之间文本的表达式就随意很多,可以用匹配单词字符的简记法“\w”表示,于是整个表达式变成:
c(?![au]t\b)\w+t
注意:这里出现的并不是排除型字符组
[^au],而是普通的字符组[au],因为顺序否定环视本身已经表示了否定。
进一步思考,整个匹配文本中都不能出现字符串“cat”,要怎么办呢?这个正则表达式应该是:
^(?:(?!cat).)+$
即在文本中的任意位置,都不能出现该字符串。
4.逆序否定环视(?
(?<![a-z])\d{7}
分析以下表达式,匹配不包含属性的简单HTML标签内的内容:
(?<=<(\w+)>).*(?=<\/\1>)
以上表达式最能表现零宽断言的真正用途。(<?(\w+)>)指定前缀为:被尖括号括起来的单词(比如可能是“<b>”),然后是“.*”(任意的字符串)最后是一个后缀(?=<\/\1>)。【注意】后缀里的“\/”,用到了前面提过的字符转义;“\1”则是反向引用,引用的正是捕获的第一组,即前面(\w+)匹配的内容,如果前缀实际上是“<b>”,后缀就是“</b>”。整个表达式匹配的是“<b>”和“</b>”之间的内容(再次提醒,不包括前缀和后缀本身)。
总体而言,环视相当于对“所在位置”附加一个条件,难点就在于找到这个“位置”。这一点解决了,环视就没有什么秘密可言了。
贪婪/懒惰匹配模式
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。例如以下表达式将匹配以a开始,以b结束的最长字符串:a.*b
如果用来搜索“aabab”,它会匹配整个字符串“aabab”。这就是贪婪匹配。
有时,需要匹配尽可能少的字符,也就是懒惰匹配。前面给出的限定符都可以转化为懒惰匹配模式,只要在后面加上一个问号。例如“.*?”就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。例如,匹配以a开始、以b结束的最短字符串,正则表达式如下:
a.*?b
把上述表达式应用于aabab,如果只考虑“.*?”这个表达式,最先会匹配到aab(1~3字符)和ab(第2~3个字符)这两组字符。
为什么第一个匹配是aab(第1~3个字符)而不是ab(第2~3个字符)?简单地说,正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高优先权。
常用懒惰限定符如表所示。
懒惰模式匹配原理简单来说,是在匹配和不匹配都可以的情况下,优先不匹配,记录备选状态,并将匹配控制交给正则表达式的下一个匹配字符。当后面的匹配失败时,回溯,进行匹配。关于回溯以及正则表达式效率等高级内容,可以查阅《精通正则表达式》一书。
例如:
<?php $str='1.gif2.gif3.gif'; $s=preg_replace("#\[url\](.*?)\[\/url\]#","<img src=http://www.test.com/upload/$1>",$str); var_dump($s);
运行结果为:
string(126) "<img src=http://www.test.com/upload/1.gif><img src=http://www.test.com/upload/2.gif><img src=http://www.test.com/upload/3.gif>"
如果改为贪婪匹配
<?php $str='1.gif2.gif3.gif'; $s=preg_replace("#\[url\](.*)\[\/url\]#","<img src=http://www.test.com/upload/$1>",$str); var_dump($s);
运行结果为:
string(74) "<img src=http://www.test.com/upload/1.gif[/url]2.gif[url]3.gif>"
在贪婪模式下,由于匹配表达式是“.*”,即任意字符出现任意次,这个正则表达式会一直匹配[url]后的内容,直到遇到结束条件“[\/”。
实际开发中,涉及贪婪模式与懒惰模式的地方是很多的。在一定情况下,使用懒惰模式可以减少回溯,提高效率。
相关文章推荐
- 正则表达式匹配规则
- 常用的正则表达式匹配规则整理
- 正则表达式――匹配规则介绍
- Javacript—正则表达式匹配规则
- 正则表达式教程-正则表达式匹配规则(6)
- 一些实用的正则表达式(匹配规则)
- 正则表达式常用匹配规则
- PHP核心技术与最佳实践之正则表达式匹配规则
- Python中正则表达式的一些匹配规则
- php学习第四章:正则表达式(三)匹配规则:简记法、转义、反义(排除)和分支
- 正则表达式――匹配规则介绍
- utf8下汉字匹配正则表达式的规则
- php学习第四章:正则表达式(四)匹配规则:分组、环视(零宽断言和负向零宽断言)
- 正则表达式匹配规则
- 正则表达式匹配规则
- 简述正则表达式匹配规则
- Python中正则表达式的匹配规则
- ios学习--正则表达式匹配规则
- JavaScript中正则表达式判断匹配规则及常用方法
- 正则表达式 - 匹配规则