引子
上一章分享了正式表达式的入门知识,以及单字符、多字符常用的匹配方法,对于工作维护过程中已经够用,但是有时候只使用基础知识来实现就会比较麻烦,如果使用高级用法就会比较方便很多。
例如:匹配一个HTML文件中两个<B>标签中的文件。
文本内容:
This offer is not available to customers living in <B>AK</B> and <B>HI</B>
从上一章内容的知识可以想到的表达式可能如下:
<[Bb]>.*</[Bb]>
但是这个表达式配置的结果是AK</B> and <B>HI,而不是我们想要的AK和HI。
懒惰型匹配
引子中的例子中的匹配方式是属于贪婪行为,就是尽可能多的匹配内容,像例子中第一个<B>和最后一个</B>中间都被匹配到了,而不管匹配内容中是否存在</B>。
上一章中讲到的*和+、{m,}都是所谓的“贪婪型”的。在这一节中讲一下与“贪婪型”相反的“懒惰型”,就是匹配尽可能少的内容。
实现很简单,就是在原有“贪婪型”元字符后面加上一个? 号,如下表格
| 贪婪型元字符 | 懒惰型元字符 |
|---|---|
| * | *? |
| + | +? |
| {m,} | {m,}? |
位置匹配
在现实的系统中一般表达位置的地方就是一个单词的开头以及结尾或者一个字符串的开头或者结尾。
注意这个边界只是一个位置,例如单词边界匹配的是\w和\W之间的一个位置
| 元字符 | 说明 | 注意 |
|---|---|---|
| \b | 单词边界,单词的开头或者单词的结尾 | 回退键的元字符是[\b]
|
| \B | 表示非单词边界 | |
| ^ | 字符串的开头位置 | 放在[]中表示取非操作 |
| $ | 字符串结尾位置 |
扩展:
- 像egrep中也支持使用
\<匹配单词开头位置,使用\>匹配单词结尾位置,但是支持这种元字符的编辑器比较少。 - (?m)是一个分行匹配模式的记号,放在一个表达式的最前面,会改变字符串位置匹配的行为。
^不仅匹配正常的字符串开头还匹配行分隔符(换行符)后面的开始位置;同样$不仅匹配正常的字符串结尾还匹配行分隔符(换行符)后面的结束位置;此用法只有部分正则表达式会支持
| 选项 | 描述 | 支持平台 |
|---|---|---|
| (?d) | Unix中的行 | java |
| (?i) | 不区分大小写 | PCRE Perl java |
| (?J) | 允许重复的名字 | PCRE* |
| (?m) | 多行 | PCRE Perl java |
| (?s) | 单行 | PCRE Perl java |
| (?u) | Unicode | java |
| (?U) | 默认最短匹配,与懒惰型匹配类似 | PCRE |
| (?x) | 忽略空格和注释 | PCRE Perl Java |
| (?-...) | 复原或关闭选项 | PCRE |
捕获分组与后向引用
前面的元字符都是对紧挨着前面的一个字符有效,例如表达式the{3}匹配theee字符串,假如我们想匹配连续三个the字符串怎么办呢,这就涉及到子表达式的概念。
子表达式
把一个表达式匹配的内容做为一个单独的元素嵌入到另外一个表达式中,那这个做为独立元素的表达式就是子表达式,需要使用()括起来。这个跟数学的表达式概念很类似。
并且子表达与数学表达式还有一个类似的地方就是,正则表达式的子表达式也可以嵌套使用
本节开头说的那个问题就可以使用子表达式来实现,
(the){3}就会匹配thethethe这个字符串。
假如我们再加个条件:我们想匹配连续三个the或者连续三个you,怎么实现?这就是正则表达式的选择操作符,也叫或操作符了
| 元字符 | 说明 |
|---|---|
| |
或操作符,两边的表达式都是一个独立的元素,一般放在()中使用 |
上面的问题就可以使用正则表达式(the|you){3}来表示
捕获分组与后向引用
当一个模式的全部或者部分内容由一对括号括起来时,就对表达式进行了分组(其实就是放在()中的子表达式),并且把分组匹配到内容捕获并且临时存放在内存中。这就是捕获分组,可以在后面表达式中使用就叫后向引用,或者叫回溯引用。
默认情况下,分组是从左到右依次排序从1编号,第一个分组就是1,第二个分组就是2等等。
最开始的时候支持的编号范围是1到9,现在应该已经没有这种限制了。
后向引用很简单就是一个\或者$后面跟相应编号即可。例如\1或者$1就表示引用第一个捕获分组。
命名分组
前面讲捕获分组都是通过位置编号来访问,在perl和python、.NET等语言中还支持对捕获分组命名。这样就比较容易理解
| 命名语法 | 描述 |
|---|---|
| (?<name>分组) | 命名分组 |
| (?P<name>分组) | python中的命名分组 |
| \k<name> | Perl中引用命名分组 |
| \k'name' | Perl中引用命名分组 |
| \g{name} | Perl中引用命名分组 |
| \k{name} | .NET中引用命名分组 |
| (?P=name) | Python中引用命名分组 |
非捕获分组
顾名思义,与捕获分组相反,就是不会将分组匹配的内容放在内存中。主要是为了提高性能。
使用方法:在分组的开头加上?:,例如(?:the)
当把非捕获分组语法中的
:换成>时,就变成了原子分组(另一种非捕获分组),可以进一步提升性能。因为原子分组会将分组内部的回溯操作关闭。
环视
环视是一种非捕获分组,它根据某个模式之前或者之后的内容要求匹配其他模式。环视也称为零宽度断言。
| 环视分类 | 说明 | 举例 |
|---|---|---|
| (?=分组) | 正前瞻,匹配且要求紧随其后内容为分组匹配的内容 |
a(?=b),匹配a并且后面坚接着是b的字符串,可以匹配abc但是不匹配acb
|
| (?!分组) | 反前瞻,即对正前瞻含义取反,匹配且要求紧随其后内容不为分组匹配的内容 |
a(?!b),匹配a并且后面坚接着不是b的字符串,可以匹配acb但是不匹配abc
|
| (?<=分组) | 正后顾,即对正前瞻方向取反,匹配且要求紧挨着之前的内容为分组匹配的内容 |
(?<=a)b),匹配b并且前面紧挨着是a的字符串,可以匹配abc但是不匹配cbc
|
| (?<!分组) | 反后顾,即对正后顾含义取反,匹配且要求紧挨着之前的内容不为分组匹配的内容 |
(?<!a)b),匹配b并且前面紧挨着不是a的字符串,可以匹配cbc但是不匹配abc
|
条件匹配
(?(id/name)yes-pattern|no-pattern)
如果给定的 id 或 name 存在,将会尝试匹配yes-pattern;否则就尝试匹配no-pattern,no-pattern可选;
例如:email样式匹配(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$),当<存在时,则最后要匹配>;否则匹配结束符$
参考
《学习正则表达式》
《正则表达式必知必会》