上一篇入门介绍了匹配单个字符的元字符和计数元字符,最后的练习里我们使用了括号,这也是正则里的元字符之一,是用来限定匹配子结构和捕获用的,有些不太好分类但是我们常用的控制结构,我们将他们放到其他元字符中。
其他元字符
来看我们常用的一些:
元字符 | 名称 | 含义 |
---|---|---|
| | alternation | 匹配分隔的表达式 |
() | 括号 | 限定结构的范围,分组,捕获计数 |
(?:……) | 限定范围和分组,但不增加捕获计数 | |
\1,\2 | 反向引用 | 匹配之前第一、第二括号内表达式匹配的内容 |
(?<Name>) | 命名捕获 | 可以使用名称来获取分组内容group(Name),而不是group(1)方式 |
(?> ) | 固化分组 | 不会交还已经匹配的内容 |
(?(if-exp) then-exp | else-exp) | 条件判断 |
-
竖线是选择元字符,可以理解成程序中的或,左右两边可以是普通的正则表达式,譬如说前面的字符组
[1-5]
,跟1|2|3|4|5
在语义上是等价的,不过我们在实现的时候能用字符组还是用字符组,因为多选结构正则引擎在没有优化的情况是会一个个匹配然后回溯的,效率上不如字符组 -
括号可以用来限定范围,分组,捕获计数,这里范围表示可以将括号内的正则看做一个整体,计数元字符就可以对整体作用,分组表示括号内的正则表示一个组,并增加捕获计数,在后面可以使用
\1
\2
等方式来反向引用前面分组正则匹配到的内容。举个例子,想找到连续两个重复单词,我们就必须要知道前面单词是什么,这时候就可以使用反向引用了,可以简单写为(\w+)\s+\1
,这里没有界定单词,但在平常我们使用已经够了,后面可以加上单词分界符更精确 -
(?:)
结构跟括号作用基本一样除了不增加捕获计数,也就是说不能使用反向引用来引用括号里的匹配内容,因为使用带捕获的括号时,正则引擎需要记录括号里的捕获内容,回溯时也需要更改状态,如果只是为了分组,我们就可以使用它来减少引擎负担,加快速度 -
命名捕获相当于将括号内匹配的内容赋值给变量,后面我们不用
\1
方式来引用,而是可以直接使用name
引用 -
固化分组,这个我们中字面上来理解就是已经匹配的内容固化,不会再吐出去让后面的表达式来匹配,使用固化分组可以使不符合条件的项尽快失败,减少重试次数。同时也会让引擎丢弃不需要保存的状态。
-
条件判断可以赋予我们分支执行的能力,目前用的不多
匹配位置元字符(锚点)
讲了其他一些控制字符,我们再来看下匹配位置的元字符,这里需要记住一点:
锚点不会匹配实际的文本,而是寻找特定的位置
也就是说锚点会去查看前后字符是否符合你的要求,但是并不占用字符
元字符 | 名称 | 含义 | 示例与注 |
---|---|---|---|
^ | 脱字符 | 匹配一行开头 | |
\A | 匹配文本的起始位置 | vim里匹配一个单词起始,notepad++匹配到下一个字符,暂未想到用处 | |
$ | 美元符 | 匹配一行结束位置 | |
\Z \z | 匹配文本结束位置 | ||
\< | 单词分界符 | 匹配单词开始 | java中使用\b |
\> | 单词分界符 | 匹配单词结束 | java中使用\B |
(?=……) | 顺序环视 | 匹配右侧文本 | (?=Jeffery)Jeff 位置定在J之前但后面必须是Jeffery |
(?<= ) | 逆序环视 | 匹配左侧文本 | (?=Jeff)ery 位置定在f之后,匹配ery, s/(?<=Jeff)(?=s)/'/g 将Jeffs变为Jef's |
(?!) | 否定顺序环视 | 不匹配右侧文本 | |
(?<!) | 否定逆序环视 | 不匹配左侧文本 |
^$
匹配一行开头与结尾,很常见,不多说\<
与\>
是单词分界符,也有使用\b
与\B
来分界的,在前面匹配重复单词时,我们就可以使用\<(\w+)\>\s*\1
来界定单词,这样this is a test test ha ha
is就不会被认为是重复单词的一个了- 环视,分顺序和逆序,肯定和否定,总共四种,要求你的文本前后需要满足环视的要求
- 顺序环视,右侧文本必须满足给定的条件,
(?=Jeffery)Jeff
里要求匹配Jeff,但是右侧必须为Jeffery,也就是说只匹配Jeffery里的Jeff,其余的Jeffary等就不能匹配 - 逆序环视,左侧文本必须满足给定条件,跟顺序环视锚点在前面不同,它的锚点在要求后面。
- 否定顺序与否定逆序环视要求左右侧文本不能是什么
- 假设我们要匹配双引号内的内容,我们可以使用
".*?"
来匹配,也可以使用"[^"]*"
来匹配,更可以使用否定顺序环视"((?!").)*"
来匹配
- 顺序环视,右侧文本必须满足给定的条件,
模式修饰符
模式修饰词 (?modifier)
元字符 | 名称 | 含义 | 示例与注 |
---|---|---|---|
(?i) | 开启不区分大小写匹配 | 应用在子表达式中 | |
(?-i) | 关闭不区分大小写匹配 | 与(?i)配合使用 | |
\Q..\E | 文字文本范围 | 之间的字符全部当做文本,不解析为元字符 |
- 在Java中可以在编译Pattern时指定不区分大小写,grep也可以使用-i来启用,但这是针对正则表达式全局的设定,如果我们要对局部进行细微控制的话,就需要用到
(?i)
来指定不区分大小写了,譬如想匹配Petter
,首字母不区分大小写,但是后续字母必须全是小写,我们就可以使用(?i)p(?-i)etter
,这样就不会匹配到PETTER
- 平常遇到元字符我们可以使用反斜线来转义,但是如果遇到大段文本,里面包含好多元字符的话,有反斜线转义显得啰嗦,而且表达式也不清晰,可以用
\Q..\E
来限定,里面的字符不会被解析为元字符
总结
本节已经把常用的元字符全部都罗列完了,Unicode相关的控制\p
等没有列出,平常用不太多,把这些融汇贯通基本就可以解决90%的正则问题了。接下来我们来探讨一下正则引擎的原理,有助于我们写出正确、效率高的正则表达式。
练习
匹配一个email,自己写一下再与网上的对照一下。
email模式,前面是名称,中间是@符,最后是机构域名
可以简单写为:[-0-9a-zA-Z_]+@\w+(\.\w+)+
这里没有做严格验证,譬如说开头不能是下划线和横线,结尾域名也不能是随意的单词,看大家的使用场景,有时候用这个就够了,有时候需要更精确一点。
加上限制,可以写为[0-9a-zA-Z]+[-0-9a-zA-Z_]*@[0-9a-zA-Z]+\.(com|cn|org|net)
,域名可以把想到的都写进去。