PHP中的递归正则使用说明_正则表达式

之前一篇文章翻译了Perl语言中的递归正则表达式. 其实不少语言中的正则都是支持递归的, 例如本文要介绍的PHP正则递归. 虽然, 工作中最常用的正则表达式都很”正则”, 只用最基本的语法就能解决85%以上的问题, 而且合理有效地使用普通正则来解决复杂问题也是一门技巧与学问; 但是高级一点的语法的确有它存的价值, 有时不用它还真办不了事儿; 况且学习正则的乐趣也在于尝试各种各样的可能性, 满足自己无穷无尽的好奇心.

本文内容, 整理自网文Finer points of PHP regular expressions. 其分析过程剥茧抽丝, 丝丝入扣, 值得一读. 该文系统地列出了PHP中正则表达式常见特性, 我只摘取其中递归部分翻译整理出来.

正文
例子
什么时候会用到递归正则表达式呢? 当然是待匹配的字串中递归地出现某种模式时(貌似废话). 最经典的例子, 就是递归正则处理嵌套括号的问题了. 例子如下.

假设你的文本中包含了正确配对的嵌套括号. 括号的深度可以是无限层. 你想捕获这样的括号组.

恕我剧透, 标准答案是这样的:

复制代码 代码如下:

<?php
$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
)

可见, 我们所需要的文本, 已经捕获到$matches[0]中了.

原理
现在思考原理.

上面的正则表达式中的关键点是(?R). (?R)的作用就是递归地替换它所在的整条正则表达式. 在每次迭代时, PHP 语法分析器都会将(?R)替换为”\(([^()]+|(?R))*\)“.

因此, 具体到上述的例子, 其正则表达式等价于:

"/\(([^()]+|\(([^()]+|\(([^()]+)*\))*\))*\)/"

但是上面的代码只适合深度为3层的括号. 对于未知深度的括号嵌套, 就只好使用这种正则了:

"/\(([^()]+|(?R))*\)/"

它不但能够匹配无限深度, 还简化了正则表达式的语法. 功能强大, 语法简洁.

现在来细看一下"/\(([^()]+|(?R))*\)/"是怎样匹配"(a(b(c)d)e)"的:

"(c)"这部分被正则式 "\(([^()]+)*\)" 匹配. 请注意, (c) 其实就相当于整个递归的一个缩影, 麻雀虽小五脏俱全, 因此它用到了整个正则表达式.
换言之, 下一步中的(c), 可以使用(?R) 来匹配.
(b(c)d)的匹配过程为:
"\("匹配"(";
"[^()]+"匹配"b";
(?R)匹配"(c)";
"[^()]+"匹配"d";
"\)"匹配")".
根据上面的匹配原理, 不难理解为什么数组的第2个元素$matches[1]与'e'等价. 子串'e'是在最后一次匹配迭代中被捕获. 匹配过程中, 只有最后一次的捕获结果才会保存到数组中.

rex注: 关于这个特性, 可以自行尝试一下, 看看使用正则式([a-z]+[0-9]+)+来匹配字串abc123xyz890, 其捕获结果$1是什么. 注意, 其结果与 Left Longest 原理并不冲突.

如果我们只需要捕获 $matches[0], 可以这样做:

复制代码 代码如下:

<?php
$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)
)

所做的改动是捕获括号()改为非捕获捕获括号(?:)了.

还可以进一步完善为:

复制代码 代码如下:

<?php
$string = "some text (a(b(c)d)e) more text";
if(preg_match("/\((?>[^()]+|(?R))*\)/",$string,$matches))
{
echo "<pre>"; print_r($matches); echo "</pre>";
}
?>

这里我们用到了所谓的一次性模式(rex注: 余晟先生译的《精通正则表达式v3.0》中, 谓之”固化分组”. 可参考该书.) PHP手册也推荐只要条件允许, 就尽可能使用这种模式, 以便提升正则表达式的速度.

一次性模式很简单, 这里不再详述. 如果感兴趣, 可以参考PHP 官方手册. 如果您想深入学习PERL兼容式正则表达式, 请参考文末链接.

原文: Finer points of PHP regular expressions
Perl兼容正则表达式 官网 文档
PHP官网的PCRE正则文档

时间: 2024-10-31 03:32:55

PHP中的递归正则使用说明_正则表达式的相关文章

PHP中的递归正则使用说明

之前一篇文章翻译了Perl语言中的递归正则表达式. 其实不少语言中的正则都是支持递归的, 例如本文要介绍的PHP正则递归. 虽然, 工作中最常用的正则表达式都很"正则", 只用最基本的语法就能解决85%以上的问题, 而且合理有效地使用普通正则来解决复杂问题也是一门技巧与学问; 但是高级一点的语法的确有它存的价值, 有时不用它还真办不了事儿; 况且学习正则的乐趣也在于尝试各种各样的可能性, 满足自己无穷无尽的好奇心. 本文内容, 整理自网文Finer points of PHP regu

编程语言中十六进制的正则匹配_正则表达式

它由0-9,A-F,组成.与10进制的对应关系是:0-9对应0-9;A-F对应10-15;N进制的数可以用0---(N-1)的数表示超过9的用字母A-F. 也就是由字符:012345679ABCDEF 组成 C语言中的16进制数必须以 0x开头.比如 0x1表示一个16进制数.而1则表示一个十进制.另外如:0xff,0xFF,0X102A,等等.其中的x也不区分大小写.(注意:0x中的0是数字0,而不是字母O) 所以C或C++等编程语言的正则匹配为: 复制代码 代码如下: \b0[xX][0-9

asp提取内容中的手机号码,qq,网址的正则代码_正则表达式

常用的正则匹配表达式 正则表达式--验证手机号码:13[0-9]{9} 实现手机号前带86或是+86的情况:^((\+86)|(86))?(13)\d{9}$ 电话号码与手机号码同时验证:(^(\d{3,4}-)?\d{7,8})$|(13[0-9]{9}) 提取信息中的网络链接:(h|H)(r|R)(e|E)(f|F) *= *('|")?(\w|\\|\/|\.)+('|"| *|>)? 提取信息中的邮件地址:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+

JS正则中的match与exec使用说明_正则表达式

主要参考W3C教程 String.match()<?XML:NAMESPACE PREFIX = O /> match() 方法将检索目标字符串,以找到一个或多个与 正则表达式regexp 匹配的文本.match()有两种情况: <!--[if !supportLists]-->1. <!--[endif]-->如果 regexp 具有标志 g,则 match() 方法将执行全局检索,找到目标字符串中的所有匹配子字符串. 匹配情况 返回值 没有找到任何匹配的子串 nul

jQuery中的正则表达式分析 正则基础_正则表达式

quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/ (?:-)表示是一个非捕获型 [^<]表示是以"<"起始,包含0个或多个'<'括号 (<[\w\W]+>)表示是一个捕获型,以'<>'起始,中间包含一个或多个字符 $表示字符的结尾 (#([\w\-]+))表示是一个捕获型,以'#'号和字符串.数字._以及-组成 rnotwhite = /\S/ \S表示是空白字符

正则表达式中\w不能识别中文_正则表达式

正则表达式用于字符串处理,表单验证等场合,实用高效,但用到时总是不太把握,以致往往要上网查一番.我将一些常用的表达式收藏在这里,作备忘之用.本贴随时会更新. 匹配中文字符的正则表达式: [\u4e00-\u9fa5] 匹配双字节字符(包括汉字在内):[^\x00-\xff] 应用:计算字符串的长度(一个双字节字符长度计2,ASCII字符计1) String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa")

js正则表达式验证URL函数代码(方便多个正则对比)_正则表达式

推荐大家收藏的一段代码,方便同时测试多个正则,查看不同的检测结果,结合chrome完美 核心代码 <script> /** * 正则表达式判断网址是否有效 */ (function(){ "use strict"; var urlDict=[ //Bad Case 'www.baidu.com', //常规网址,未带协议头的地址 'w.baidu.com', //常规网址,短子域名 'baidu.com', //常规网址,仅有主域名 '测试.com', //非常规合法网址,

正则表达式单行、多行模式简介(使用说明)_正则表达式

继上几篇正则表达式相关说明(详情:正则表达式 ),我们今天继续讨论下,它的单行,多行模式使用,及容易出现错误地方.单行,多行模式,都是正则表达式的模式修饰符里面出现的参数.目前常用正则表达式都有该使用选项,如:javascript 正则表达式,一般是:"/正则表达式匹配字符/修饰符" ,最后一个"/" 后面是修饰符.然后,php也是类似的,c#,python等,一般调用正则表达式的匹配函数,都有一个另外选项的,设置模式. 单行.多行模式容易出现理解错误 为什么说,容

Javascript 字符串字节长度计算函数代码与效率分析(for VS 正则)_正则表达式

先看看一下两段代码吧,它们分别用for循环和正则表达式来检测字符串的字节长度: for循环检测字符串的字节长度方法一: 复制代码 代码如下: var lenFor = function(str){ var byteLen=0,len=str.length; if(str){ for(var i=0; i<len; i++){ if(str.charCodeAt(i)>255){ byteLen += 2; } else{ byteLen++; } } return byteLen; } els