转载

正则笔记之括号

概述

本文主要介绍正则表达式中的分组、多选结构和引用分组, 补充 Possessive量词

分组

把一个表达式用括号包围起来, 这个元素就是括号里的表达式, 括号内的表达式通常被称为子表达式。括号的这种功能称为分组。

实例: 使用正则匹配价格,小数最多为两位, 例如: ¥189.90 、 $ 49 、 88.00 和 121 等。

货币符号 ¥ 或 $ 都是可能出现也可能不出现,所以正则应该写成 [¥/$]? , 具体价格之间还可能存在多个空格 所以使用 /s* , 价格的小数部分可能出现也可能不出现, 使用括号包裹成一个子表达式 (/./d{1,2})? 。综上,价格的正则表达式应该写成 [/$¥]?/s*/d+(/./d{1,2})?

多选结构

使用多选结构可以列出所有的可能结果, 只要其中的一个子表达式匹配上了整个多选结构的匹配就是成功了; 除非多选都匹配失败了, 那么这个多选结构就匹配失败。多选结构的形式是 (...|...) ,中间使用多个 | 分隔开多个子表达式。

实例: 匹配上小时(0-24)、分(00-60)和秒(00-60), 例如 00:01:59。

分析: 小时, 当小时为个位数时, 十位数上的 0 可以存着也可以不存在;当十位数为 1 时, 个位数可以匹配所有的 0-9; 十位数为 2时, 个位数上可以匹配 0-4之间的数, 所以小时的正则可以写作 (0?/d|1/d|2[0-4]) ; 同理可以推断出 分或秒的正则表达式为 (0?/d|[1-5]/d|60)

注意:

  1. 多选结构一般的表达式是  (option1|option2) , 还有一种写法  option1|option2 。但是第二种形式容易混淆, 一般不采用第二种写法(详细请见本文的优先级部分内容)。

  2. 多选分支并不等于字符组。字符组比多选结构看起来要简洁的多, 而且多选结构不支持 - 范围表达式。一般情况下, 能用字符组解决的问题, 就不使用多选分支结构来处理。

  3. 多选分支的排序是有讲究的。大部分语言的多选结构都会优先选择最左侧的分支。如果多选分支的排序不合理会存在重复匹配,这样会增大回溯的计算量。所以多选分支的排序是需要好好研究的。

  4. 关于转义: 无论分组还是多选结构, 使用普通字符时需要全部转义也就是说  (|) 对应的转义为  /( /| /)

引用分组

上文使用括号后, 正则表达式会保存每个分组真正匹配的文本, 等匹配结束以后, 可以通过 group(num) 之类的方法获取扥组在匹配时捕获的内容, 这种功能叫做捕获分组。num 对应分组的编号, 分组编号从 1 开始, 默认存在 编号 0 匹配整个表达式。

注意: 无论括号嵌套多少层, 分组的编号是根据开括号出现的顺序来计数的, 开括号从左向右暑期第多少个开括号, 整个括号分组的编号就是多少。

引用分组还可以应用于替换。Java 、 JavaScript 替换的引用编号写法是 $num , 而 Python 的写法确是 /num , 不同语言请注意区别。

注意: 像 Python 一样使用 /num 形式来进行正则替换的语言, 不用使用 /0, 因为 /0 开头的转义序列通常表示八进制形式的字符。

反向引用

在正则表达式内部使用引用之前的捕获分组匹配的文本, 其形如 /num 其中 num 为分组的编号, 规则同之前介绍的相同, 这种结构称为反向引用。

注意:

  1. 反向引用重复的是对应捕获分组捕获的文本, 而不是之前的表达式。

  2. 捕获的二义性:  /10 (或  $10 ) 是表示第十个捕获分组还是第一个捕获分组和 0 ?Python 中提供  /g<num> 表示法,  /10 表示第十个捕获分组,  /g<1>0 表示第一个分组和 0 。Java 和 JavaScript 规定  /num 如果是一位数则对应捕获分组; 如果是两位数且存在对应捕获分组则对应捕获分组; 如果为两位数且不存在对应引用分组则为一位数编号的捕获分组。

命名分组

像 Java 中如果两位数且存在对应捕获分组就无法使用 /10 表示第一个捕获分组和 0 。为了解决这个可以使用命名分组。由于兼容性问题,使用命名分组对原先的编号规则没有影响。

Python的命名分组形式为 (?p<name>regex) 。在表达式中使用反向引用, 必须使用 (?p=name) 的形式; 进行替换是使用 /g<name> 形式。

Java 7 开始支持命名分组, Java 的命名分组形式是 (?<name>...) , 表达式中反向引用形式为 /k<name> , 替换时使用 ${name}

JavaScript 从 ES2017 开始支持命名分组, JavaScript 的命名分组形式是 (?<name>...) , 表达式中反向引用形式为 /k<name> , 替换时使用 $<name>

非捕获组

前文介绍的分组和多选结构都可以被引用。引用会严重影响性能。为了减少对性能的影响引入了非捕获组。形式为 (?:...)

如果只需要使用括号的分组或者多选结构的功能, 而没有用到引用分组, 则应该使用非捕获组。将捕获组括号修改为非捕获组括号后, 引用分组对应的编号可能也要调整, 这点要注意。

Possessive 量词

之前在介绍量词时遗漏 Possessive 量词。量词内容详见:正则笔记之量词 。

Possessive 量词 类似懒惰量词。它通过检查整个字符串来只是启动引擎。如果匹配失败不会进行回溯。

量词的形式

量词 说明
{n}+ 之前的元素必须出现 n 次
{m,n}+ 之前的元素至少出现 m 次, 至多出现 n 次
{m,}+ 之前的元素至少出现 m 次, 出现次数无上限
{0,n}+ 之前的元素可以不出现, 也可以出现, 最多出现 n 次
*+ 等价于 {0,}
++ 等价于 {1,}
?+ 等价于 {0,1}

实例

优先级

优先级 组合 说明
1 (regex) 真个括号内的子表达式称为单个元素
2 * ? + 量词限定之前紧邻的元素
3 abc 普通拼接, 元素相继出现
4 a bc

注: 数字越小, 优先级越高

实例:

多选结构的括号可以省略, 但是容易混淆。比如 : ^(ab|cd)$^ab|cd$ 第一眼很大概率会以为这两个正则表达的意思相同, 但是后者实际上等价于 (^ab|cd$) 。所以多选结构中括号一定不能省略。如果不需要捕获文本, 应当把普通括号 (...) 改为 非捕获括号 (?:...)

原文  https://mp.weixin.qq.com/s/ybd3B2U22A-tL61c6Ru9Jw
正文到此结束
Loading...