正则表达式中的子组模式

作者:西瓜玩偶(racnil070512 at hotmail dot com) 一、基础知识 在PCRE正则表达式中,我们可以利用圆括号定义一个子组,我们可以使用preg_match函数(其他函数的信息请参考PHP官方API文档)的第三个参数捕获圆括号中匹配的内容: preg_match('#color\h*:\h*([A-Za-z]*)#', 'color: red', $matches); print_r($matches); 运行的结果为: Array ( [0] => color: red [1] => red ) 根据定义,子组(正则表达式中圆括号)中的内容会按照左半边括号出现的顺序,将匹配的内容分别存放至$matches数组中,下标从1开始(下标0的内容为整个匹配的字符串)。 这个特性可以让我们很方便地从被匹配的字符串中提取我们需要的信息。PCRE中的子组的功能其实非常强大,但是PHP官方的API文档并没有对齐作过多的介绍。下面的文章尝试对PCRE中的子组功能做一个初步的介绍。 二、匹配顺序 子组其中一个重要的作用就是用来描述“分支”的匹配,但是如果较短的分支是较长分支的前缀的话,那么较短的分支一定要放在较长的分支后面: '#(eq|lte|gte|lt|gt)#'

注意,这里的lt必须放在lte的后面,否则的话正则表达式解析器读到lt时分支就已经匹配成功了,那么lte就永远不会被匹配到。 三、非捕获子组 有些时候子组只是用来描述“分支”的匹配的,我们并不想让最后的$matches里面出现括号里的内容,此时可以用非捕获子组(?:)告诉正则表达式解析器,它不需要被捕获: '#(?:https?|ftp)://([A-Za-z\.]+)#'

这样,URL里面主机名部分就会被存放至$matches数组下标为1的域内。而前面的https?|ftp虽然也被打了圆括号,但是由于圆括号中有?:,所以并不会被保存到$matches中。 不过这里仅仅是举例子,在实际应用中,可以调用parse_url函数来更好地完成获取主机名的任务。 四、前向探测(Lookahead) 前向探测的目的是,在当前的点,向后读入内容(对于读取匹配内容的程序来说,它即将读入的内容被称为“前”;但是对于阅读者来说,即将读入的内容被 称为“后”),判断其是否与子组中的正则表达式相匹配。如果匹配,则继续匹配后面的内容,否则匹配失败。虽然前向探测会向后读入内容,但是被读入的内容并 不会被“消耗”掉,也不算做正则表达式匹配的一部分,也就是说,后面的正则表达式依然可以匹配到向后读入的内容。 如果这样说不太明白,可以看看下面的例子。利用(?=)就可以构造一个前向探测: '#\d*(?= mm)#' 这个正则表达式会匹配如'100 mm'这样的字符串。由于前向探测的正则表达式mm并不属于正则表达式的一部分,所以最后整个表达式(注意,不是$matches下标为1的域,而是整个表达式,也就是下标0)匹配出来的结果是'100'。 更好的例子是检查密码是否符合规范: '#^(?=\w{8,20}$)(?=[^A-Z]*[A-Z])(?=[^a-z]*[a-z])(?=\D*\d)(?=[^_]*_).*$#' 这个正则表达式在最开头的地方依次使用了5个前向探测子组,分别检查密码长度在8至20之间、含有大写字母、含有小写字母、含有数字以及含有下划线。只有当这五个条件都满足,正则表达式才会继续向下匹配。由于这些子组都不会消耗读入的内容,所以最后我们简单地使用一个.*就可以获取整个密码字符串。 五、前向逆探测(Negative Lookahead) 与前向探测类似,只不过子组中的表达式必须不满足才行。它的构造方法为(?!): '#\d*(?!\d| mm)#'

这个表达式除了类似于'100 mm'以外其余的类似于'100 cm'这样的字符串都可以被匹配。注意子组正则表达式里面加了一个\d,因为不加它,当读入'100 mm'的时候,表达式还是会匹配到'10',这是因为'0 mm'不匹配' mm'。 六、后向探测(Lookbehind) 与前向探测类似,后向探测只不过是以当前点为准,向前读入内容。后向探测的构造方法为(?<=): '#(?<=EUR ).*#' 这个正则表达式会匹配'EUR 100'这样的字符串。匹配结果为'100'而不是'EUR 100',这是因为后向探测是以当前点为准,向前读入内容,这也就意味着,当开始进行最后.*的匹配时,'EUR '早已被读过了。 不过这并不意味着后向探测会消耗内容,只是因为我们并没有在正则表达式中匹配'EUR '而已。如果你有兴趣,可以尝试下面的表达式: '#EUR (?<=EUR)\d*#' 这样,匹配出来的结果就是'EUR 100'了。 七、后向逆探测(Negative Lookbehind) 与后向探测类似,只不过子组内的表达式必须不匹配。这里就不再举例了。 八、命名子组 我们可以利用下面的语法命名一个子组: '#(?P<prefix>A+)C#'

它会匹配类似于'AAAAC'的字符串,子组匹配的内容'AAAA'不仅会以数字下标保存(这个例子中为1),亦会以字符串下标('prefix')保存在$matches里面。 九、子组的重复利用 利用下面的方式我们可以重复利用已经在正则表达式中出现的子组: '#(\w+) (?1)#' 这个正则表达式会匹配'foo bar'。不过需要注意的是,重用的子组并不会被捕获。如果想要捕获重用的子组,则应该在子组外面再加上一个括号: '#(\w+) ((?1))#' 我们甚至可以通过子组名称来重复利用它: '#(?<pattern>\w+) (?&pattern)#' 甚至还可以递归地调用子组: '#(\w+, (?1)?)(\w+)#' 上面的表达式会匹配'foo, bar, baz, qux'。 十、重置分支 这一点在PHP官方文档中已经提到了: '#(?:(Sat)ur|(Sun))day#' 当匹配'Sunday'的时候,我们会发现在$matches里面下标为1的域是空的,这是因为它尝试过匹配(Sat),由于没有匹配到内容,所以它在$matches里面加入了一个空的匹配项。如果要去掉这个恼人的匹配项,我们需要在匹配不成功的时候重置分支: '#(?|(Sat)ur|(Sun))day#' 将原来的冒号改为竖线之后,我们就会发现,原来空的匹配不见了。 十一、总结 上面的文章中介绍了PCRE中子组的使用方法,并且简单地介绍了九种子组的特殊功能。如果能够灵活地、适当地运用在我们的程序中,它就可以帮助我们省掉许多字符串处理的步骤。 参考资料:http://www.rexegg.com/regex-disambiguation.html

原文发布于微信公众号 - php(phpdaily)

原文发表时间:2016-03-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我的博客

PHP5.3~PHP5.5新特性汇总

一.PHP 5.3中的新特性 1. 支持命名空间 (Namespace) 2. 支持延迟静态绑定(Late Static Binding) 3. 支持got...

3508
来自专栏编程

30个基本的Python技巧和窍门程序员

1.就地交换两个数字。 Python提供了一种直观的方式来分配和交换一行。请参考下面的例子。 x,y = 10,20print(x,y) x,y = y,xpr...

1947
来自专栏闵开慧

junit入门实例

1 junit测试用例代码 package junitTest; import static org.junit.Assert.*; import org....

3489
来自专栏LeoXu的博客

Tapestry 教程(五)实现Hi-Lo猜谜游戏

在这个游戏中,计算机会选择一个介于1到10之间的数字。你尝试猜出这个数字,点击一些链接。最后,计算器会告诉你确认目标数字你需要猜多少次。即使是像这样一个简单的示...

972
来自专栏蓝天

Dash与Bash的语法区别

本文系转载,原文URL为:http://www.igigo.net/archives/169

692
来自专栏用户2442861的专栏

#define和typedef的用法与区别及面试问题

在C/C++语言中,typedef常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间,实例像:

1071
来自专栏IMWeb前端团队

nodejs中错误捕获的一些最佳实践

本文作者:IMWeb yisbug 原文出处:IMWeb社区 未经同意,禁止转载 本文内容大部分来自 https://www.joyent.com/...

1896
来自专栏从零开始学自动化测试

Selenium2+python自动化66-装饰器之运行失败截图

前言 对于用例失败截图,很多小伙伴都希望在用例执行失败的时候能自动截图,想法是很好的,实现起来并不是那么容易。 这里分享下我的一些思路,当然目前还没找到完美的解...

3234
来自专栏IMWeb前端团队

当JavaScript遇上UINT64

导语:写下这篇文章的缘由是因为在项目过程中,碰到了一个使用JavaScript处理 UINT64 类型数字的坑。 与大部分现代编程语言(包括几乎所有的脚本语言...

2160
来自专栏恰同学骚年

《C#图解教程》读书笔记之二:存储、类型和变量

  (1)C程序是一组函数和数据类型,C++程序是一组函数和类,而C#程序是一组类型声明;

633

扫码关注云+社区