2019独角兽企业重金招聘Python工程师标准>>>

分组

小括号(以下称为括号)可以提供的第一个功能就是分组(grouping)
      通过之前的学习,可以知道量词用于限定之前元素的出现次数,这个元素可能是一个字符,也可能是一个字符组,还可能是一个表达式。如果把一个表达式用括号包围起来,就形成了括号内的表达式,而这种表达式通常被称为“子表达式”。
      换句话说,子表达式就是利用括号分组功能形成的表达式。
      比如,(ab)+表示希望字符串ab重复出现一次以上。(对比ab+的含义)
      分组的作用是可以准确表达“长度只能是m或 n”的语义。
分组功能使用场景:

  • 身份证号码的准确匹配 -- ^[1-9]\d{14}(\d{2}[0-9x])?$
  • Open tag的(较)准确匹配 -- ^<[^/]([^>]*[^/])?>$
  • Web 服务中的 URL rewrite 功能 -- 略
  • E-mail地址匹配 -- ^[-\w]{0,64}@([-a-zA-Z0-9]{1,63}\.)*[-a-zA-Z0-9]{1,63}$

上述每一项的匹配规则,以及后文中其他正则匹配规则,请自行参考《正则指引》书中说明。

多选结构

解决15和18位身份证匹配问题其实有两种思路。第一种(上面给出的正则表达式),是将18位号码多出的3位“合并”到匹配15位号码的表达式中。第二种,则是直接将15位和18位身份证号分开处理。同样还是使用括号,但使用的是括号的第二个功能,多选结构(alternative)
      多选结构的形式是(…|…),即在括号内以竖线|分隔开多个子表达式,这些子表达式也叫做多选分支。在一个多选结构内,多选分支的数目没有限制。在匹配时,整个多选结构被视为单个元素,只要其中某个子表达式能够匹配,整个多选结构的匹配就成功。如果所有子表达式都不能匹配,则整个多选结构匹配失败。
      所以,身份证号码匹配的第二种方式为:([1-9]\d{14}|[1-9]\d{14}\d{2}[0-9x])
多选结构使用场景:

  • 匹配IP地址的一段 -- ([0-9]|[0-9]{2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
  • 匹配手机号 -- (0|\+86)?(13[0-9]|15[0-356]|18[025-9])\d{8}
  • 匹配月、日、小时、分钟 -- 略
  • tag的准确匹配 -- <(‘[^’]*’|”[^”]*”|[^’”>])+>

关于多选结构的补充说明:

  • 多选结构的一般表示法是(option1|option2),一般会同时使用括号()和竖线|,但是如果没有括号,只出现竖线,仍然是正确的多选结构。在多选结构中,竖线用来分隔多选结构,而括号用来规定整个多选结构的范围,如果没有出现括号,则将整个表达式视为一个多选结构。所以ab|cd等价于(ab|cd)。推荐明确写出两端的括号,可以更加形象,也能避免一些隐晦的错误(因为竖线的优先级很低)。
  • 多选分支并不等于字符组,虽然看起来类似字符组,如[abc]能匹配的字符串和(a|b|c)一样。从理论上讲,可以完全用多选结构来替换字符组,但这种做法不被推荐。理由:首先,[abc]比(a|b|c)简洁,字符组有范围表示法;其次,大多数情况下,[abc]比(a|b|c)的效率要高的多。反过来,多选结构不一定能对应到字符组。因为字符组的每个“分支”的长度相同,而且只能是单个字符;而多选结构的每个“分支”的长度没有限制,甚至可以是复杂的表达式,比如(abc|b+c*ab),字符组对这种匹配完全无能为力。
  • 字符组有排除型字符组,但多选结构却没有对应的结构来表达该语义。
  • 多选分支的排列是有讲究的。例如,表达式(jeff|jeffrey),用它匹配jeffrey,结果是jeff还是jeffrey呢?这个问题其实没有标准答案,大多数语言的多选结构都会按照优先选择最左侧的分支来进行匹配。
  • 在平时使用中,如果出现多选结构,应当尽量避免多选分支中存在重复匹配,因为这样会大大增加回溯的计算量。

引用分组

括号不仅仅能把有联系的元素归拢起来并分组,还有其他作用,即使用括号之后,正则表达式会保存每个分组真正匹配的文本,等到匹配完成后,可以通过group(num)之类的方法“引用”分组在匹配时捕获的内容。其中num表示对应的编号,括号分组的编号规则是从左向右计数,从1开始。这就是括号的第三个功能,捕获分组(capturing group)。这种括号叫做捕获型括号

      一般来说,正则表达式匹配完成之后,都会得到一个表示“匹配结果”的对象,对它调用获取分组的方法,传入分组编号num,就可以得到对应分组匹配的文本。num的编号是从1开始的,不过编号为0的分组也是默认存在的,其对应整个表达式匹配的文本。
      关于括号存在嵌套的问题:无论括号如何嵌套,分组的编号都是根据开括号出现顺序来计数的,开括号是从左向右起第多少个开括号,整个括号分组的编号就是多少。

引用分组是使用场景:

  • 提取HTML中的所有超链接的地址和文本 -- <a\s+href\s*=\s*[“’]?([^”’\s]+)[“’]?>([^<]+)</a>
  • 提取HTML中的标题相关内容 -- <head>([^<]+)</head>
  • 提取HTML中的图片链接相关内容 -- <img\s+[^>]*?src=[‘”]?([^”’\s]+)[‘”]?[^>]*>

新手容易犯的错误
在提取日期 2010-12-22 这个内容时,正确的正则表达式为
(\d{4})-(\d{2})-(\d{2})
而错误的表达式可能被写成
(\d){4}-(\d{2})-(\d{2})

引用分组捕获的文本,不仅仅用于数据提取,也可以用于替换
      在各种语言中都有类似substitute(pattern,replacement,string)这种形式的正则替换命令。其中replacement处可以使用引用分组,形式是\num(有些语言中为$num,下文同,本文中只写作\num),其中num为对应分组的编号。同时要明白replacement处为普通字符串,在某些语言中,需要特别指明replacement使用原生字符串才行,否则\num这种引用分组在普通字符串中会被当做不合法的转义序列处理。
      另外,如果想在replacement中引用整个表达匹配的文本,不能使用\0,即便使用原生字符串也不行。一种变通策略是给整个表达式加上一对括号,之后使用\1来引用。

反向引用

反向引用(back-reference)主要用于检测重叠出现的内容,即允许在正则表达式内部引用之前的捕获分组匹配的文本。其使用形式也是\num。
反向引用的使用场景:

  • 检查某个单词是否包含重叠出现的字母 -- ^([a-z])\1$
  • 解析HTML代码时匹配简单tag -- <([^>]+)>[\s\S]*?</\1>
  • 解析HTML代码时匹配复杂tag -- <([a-zA-Z0-9]+)(\s[^>]+)?>[\s\S]*?</\1>
  • 处理中文文本 -- 略

注意:反向引用重复的是对应捕获分组匹配的文本,而不是之前的表达式,也就是说,反向引用的是由之前表达式决定的具体文本,而不是符合某种规则的未知文本。

各种引用的记法

上面说过,对分组进行引用的时候使用\num这类表示法,同时还说过,还有另外一些语言使用了不同的表示方式。如下图所示

      一般情况下,我们认为$num要好于\num。原因在于,$0可以准确表示“第0个分组(也就是整个表达式匹配的文本)”,而\0则不行,因为不少语言的字符串中,\num本身可能就是一个有意义的转义序列,表示值为num的ASCII码字符,所以\0会被解释成“ASCII编码为0的字符”。

      二义性问题:无论采用\num还是$num进行引用都会遇到二义性问题,如果出现了\10,它到底表示第10个捕获分组\10,还是第1个捕获分组\1之后跟着一个字符0?
      各种语言的解决方式有所不同,Python和PHP中提供了特定的表示法来专门解决这类问题(细节略)。而Java、Ruby和JavaScript中则是通过制定规则来解决这类问题的:如果是一位数,则引用对应的捕获分组;如果是两位数且存在对应捕获分组时,引用对应的捕获分组,如果不存在对应的捕获分组,则引用一位数编号的捕获分组。
      简单思考一下就可以发现,后面这几种语言的策略存在一个问题无法解决:如果存在编号为10的捕获分组,无法用\10表示“编号为1的捕获分组和字符0”,因为此时\10表示的必然是编号为10的捕获分组。在实际开发中,尤其是进行文本替换时有时确实会遇到这个问题,在现有的规则下是无解的。所幸的是,一般情况下,我们根本不会用到太多的捕获分组(即基本上用不到\10以上的分组);另外,已经有越来越多的语言提供了命名分组,它可以彻底解决这个问题。

命名分组

使用数字编号来标识捕获分组存在不够直观,括号多了容易搞错序号,引用时不够方便的问题。所以,一些语言和工具提供了命名分组(named grouping),可以将它看成另一种捕获分组。
      命名分组在每种语言中各有不同,此处不做展开。另外,由于历史原因和后向兼容性,即便使用了命名分组,每个命名分组同时也具有数字编号,其编号规则没有变化。

非捕获分组

前面介绍了括号的三种主要用途,需要指出的是,各种用途间不是彼此独立的,而是互相重叠的:单纯的分组可以视为“只包含一个多选分支的多选结构”;整个多选结构也会被视为单个元素,可以由单个量词限定。最重要的是,无论是否需要引用分组,只要出现了括号,正则表达式在匹配时就会把括号内的子表达式存储起来,提供引用。如果并不需要引用,保存这些信息无疑会影响正则表达式的性能;如果表达式比较复杂,要处理的文本又很多,更可能严重影响性能。
      为解决这种问题,正则表达式提供了非捕获分组(non-capturing group),非捕获分组类似普通的捕获分组,只是在开括号后紧跟一个问号和冒号(?:…),这样的括号叫做非捕获型括号。它的作用只是限定量词的作用范围,不捕获任何文本。在引用分组时,分组的编号同样会按照开括号出现的顺序从左向右递增,只是必须以捕获分组为准,非捕获分组会略过。

补充

括号的转义

括号的转义要求和括号相关的所有三个元字符(、)、|都必须转义\(、\)、\|。

URL Rewrite

URL Rewrite是常见web服务器都具备的功能,用来进行网址的转发。例子如下

外部访问 URL
http://www.example.com/blog/2006/12
内部实现
http://www.example.com/blog/posts.php?year=2006&month=12

这样重写的好处是隔离了外部接口和内部实现,方便修改;也有利于提供更有意义、更直观的URL。
      一般来说,URL Rewrite都是使用转发规则实现的,每条转发规则对应一类URL,以正则表达式解析并提取出所有需要的信息,重组之后再转发。
      学习URL Rewrite的使用可以参考当前的各类主流Web服务器的配置,此处略。

总结

本篇将《正则指引》的第三章内容进行了概括总结,下次讲解正则表达式中的断言。

参考资料

《正则指引》:余晟。

转载于:https://my.oschina.net/moooofly/blog/377757

【读书】正则指引-3-括号相关推荐

  1. php 匹配括号的个数,php 正则匹配括号内容 PHP实现正则匹配所有括号中的内容

    正则表达式:(?<=[)[^]]+ 注:以匹配中文括号中内容为例,如果匹配非中文括号,则需要在括号前增加转义符 PHP实现示例: $strSubject = "abc[111]abc[ ...

  2. php 正则 括号内容_PHP怎么实现正则匹配所有括号中的内容

    PHP实现正则匹配所有括号中的内容的方法:首先创建一个PHP文件:然后输入PHP正则匹配代码,如:"preg_match_all($strPattern, $strSubject, $arr ...

  3. php获取括号中的内容,PHP实现正则匹配所有括号中的内容

    PHP实现正则匹配所有括号中的内容 正则表达式:(?<=[)[^]]+ 注:以匹配中文括号中内容为例,如果匹配非中文括号,则需要在括号前增加转义符 PHP实现示例: $strSubject = ...

  4. php 正则 花括号,JS+正则取得小括号、中括号及花括号内容步骤详解

    这次给大家带来JS+正则取得小括号.中括号及花括号内容步骤详解,JS+正则取得小括号.中括号及花括号内容的注意事项有哪些,下面就是实战案例,一起来看一下. JS 正则表达式 获取小括号 中括号 花括号 ...

  5. 正则匹配英文括号( 中文括号【 里面的内容 封装工具类

    前言 日常开发中遇到的一个需求: String msg="这是一个字符串,括号在后面(这是括号里面的内容,你能把我取出来吗?),括号在前面,第二个括号(咋咋咋),第三个括号[这是中文中括号] ...

  6. js php 正则差别,正则表达式(括号)、[中括号]、{大括号}的区别小结,正则表达式小结...

    正则表达式(括号).[中括号].{大括号}的区别小结,正则表达式小结 正则表达式的() [] {}有不同的意思. () 是为了提取匹配的字符串.表达式中有几个()就有几个相应的匹配字符串. (\s*) ...

  7. 正则只保留括号里的内容

    场景:公司框架有一个前台导出功能,实际上是把HTML结构通过正则去掉一些没用的样式,然后由统一的工具导出. 但是不支持列表为下拉的样式,比如这种: 观察了工具:是这样的 <%@page cont ...

  8. php 正则匹配小括号

    当要匹配的字符串中存在小括号时,转义,避免当做匹配原子 或者 将 ( 处理成 (?:\() PS: (?:字符) 表示不捕获这个字符. // 文件名中存在多层序号,组装正则表达式时对序号的括号进行转义 ...

  9. Java正则获取小括号中的内容_java正则表达式获取大括号小括号内容并判断数字和小数亲测可用...

    获取大括号小括号内容 项目开发用到了,暂做个简单记录 private static String regex = "\\{([^}]*)\\}";//匹配大括号 private s ...

最新文章

  1. 在CentOS 7.7 x86_64上安装python3.7.7
  2. Nature 子刊:三代测序的DNA提取和宏基因组学分析
  3. Android 抽屉效果的导航菜单实现
  4. 【烙铁使用规范】—— 烙铁使用、温度测量规范
  5. 【学习笔记】27、面向对象学习
  6. c语言求圆锥的表面积和体积_新人教版六年级下册第三单元《圆柱和圆锥》知识整理...
  7. 震惊 | 某公司实习生跑路,竟为了学习偷盗面试题
  8. cacti mysql-bin_Cacti环境搭建(LNMP环境)
  9. 分析“HTTP500内部服务器错误”解决方法
  10. spring REST中的内容协商(同一资源,多种展现:xml,json,html)
  11. 【渝粤教育】广东开放大学 跨文化商务沟通 形成性考核 (39)
  12. Spring对字段和集合的注入---依赖注入
  13. python判断素数(质数):for-else循环的理解与示例应用
  14. ADXRS620/642/646发布:在PX4上我们为什么扔掉了MPU6000这个IMU
  15. phpmyadmin 下载
  16. 支付宝退款申请PHP,使用:4、退款查询
  17. 常见的一句话muma
  18. matlab数据变成一列数据,matlab读取excel表格列数据-matlab导入excel后,怎么把数据提取成一列?...
  19. eclipse如何汉化--安装各国语言包
  20. fatal: the remote end hung up unexpectedly (curl 56 OpenSSL SSL_read:SSL_ERROR_sysCALL)

热门文章

  1. 知识图谱(四)——实体识别和扩展
  2. matlab上位机串口通信,MATLAB GUIDE 上位机串口通信开发 绘制图形
  3. android布局layout,Android布局(FrameLayout、GridLayout)
  4. 喜报!神策数据入选“中国科创企业百强榜”前 3 名
  5. 桑文锋的数据“长征”
  6. SQL Server数据库中使用sql脚本删除指定表的列
  7. 你真的会写留言功能吗?
  8. WPF安装打印机驱动后PrintDialog 执行打印事件
  9. SpringBoot集成Mybatis(0配置注解版)
  10. 2018 Multi-University Training Contest 6-oval-and-rectangle(hdu 6362)-题解