正则表达式中的子组模式

作者:西瓜玩偶(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 条评论
登录 后参与评论

相关文章

来自专栏MelonTeam专栏

深入理解C++11(一)

导语 从最初的代号C++0x到最终的名称C++11,C++的第二个真正意义上的标准姗姗来迟。 C++11是一种新语言的开端。虽然设计C++11的目...

1819
来自专栏程序员叨叨叨

5.3 结构类型

Cg 语言支持结构体(structure),实际上 Cg 中的结构体的声明、使用和 C++ 非常类似(只是类似,不是相同)。一个结构体相当于一种数据类型,可以定...

492
来自专栏青玉伏案

窥探Swift之使用Web浏览器编译Swift代码以及Swift中的泛型

   有的小伙伴会问:博主,没有Mac怎么学Swift语言呢,我想学Swift,但前提得买个Mac。非也,非也。如果你想了解或者初步学习Swift语言的话,你可...

1745
来自专栏写代码的海盗

维多利亚的秘密 golang入坑系列

原文在gitbook,字字原创,版权没有,转载随意。 在写本文的前一天,2017维密在上海开始了。 为了纪念屌丝界的盛世,特为本节起名维多利亚的秘密。现在的社会...

3378
来自专栏python学习指南

python生成式

本篇将介绍Python的列表生成式,更多内容请参考:Python列表生成式 列表生成式即List Comprehensions,是Python内置的非常简...

1878
来自专栏Python小白进阶之旅

记录一个python里面很神奇的操作,对一个包含列表的元组进行增量赋值

今天记录一个很神奇的操作。关于序列的增量赋值。如果你很熟悉增量赋值,你也不妨看下去,我想说的是有关于增量赋值和元组之间一种神奇的操作。来自 **《流畅的Pyth...

742
来自专栏阮一峰的网络日志

Javascript编程风格

Douglas Crockford是Javascript权威,Json格式就是他的发明。 去年11月他有一个演讲(Youtube),谈到了好的Javascrip...

3176
来自专栏静晴轩

JavaScript 字符串实用常操纪要

JavaScript 字符串用于存储和处理文本。因此在编写 JS 代码之时她总如影随形,在你处理用户的输入数据的时候,在读取或设置 DOM 对象的属性时,在操作...

3537
来自专栏GopherCoder

专栏:003:正则表达式

1567
来自专栏小詹同学

Leetcode打卡 | No.017 电话号码的字母组合

欢迎和小詹一起定期刷leetcode,每周一和周五更新一题,每一题都吃透,欢迎一题多解,寻找最优解!这个记录帖哪怕只有一个读者,小詹也会坚持刷下去的!

893

扫描关注云+社区