前言
无论是写代码还是脚本,当我们要处理字符串或者提炼重要信息的时候,正则表达式都可以是我们的好帮手。
不过很多同学都有一种这样的感触,正则 = 天书 ,比如下面的邮箱表达式:
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
不知道你有没有晕,反正第一次我看的时候,这啥玩意!
于是,当我们要使用正则的时候,第一选择肯定是网上复制已有的正则表达式,网上没有?不好意思,只能选择无脑一把梭的方法。
比如我在写脚本的时候,要获取当前 maven 库中最新版本的信息,都是从 maven-metadata.xml 获取 <latest>
标签 :
<metadata>
<groupId>QDReader.QDAarCenter</groupId>
<artifactId>QDUI_Component</artifactId>
<versioning>
<latest>0.0.10</latest>
<release>0.0.10</release>
<versions>
<version>0.0.1</version>
<version>0.0.2</version>
<version>0.0.3</version>
<version>0.0.4</version>
<version>0.0.5</version>
<version>0.0.6</version>
<version>0.0.7</version>
<version>0.0.8</version>
<version>0.0.9</version>
<version>0.0.10</version>
</versions>
<lastUpdated>20210817095518</lastUpdated>
</versioning>
</metadata>
之前,我是先获取 <latest>
那一行,再使用 <>
做切割,最后取切割后的第三条,步骤属实有点多。
而有了正则,通过 (?<=latest>)[\d\.]+
就可以获取到我想要的内容!
先分享给大家一个学习正则表达式的宝藏网站:
这个网站可以输入正则表达式和你想验证的文本,并将匹配的内容高亮,甚至会告诉你为什么会这么匹配,当然还有一些其他小技巧:
网站地址:https://regex101.com/r/lL3C8c/2
目录
一、如何匹配字符
方括号 []
的使用频率很高,括号中的内容是用来匹配的,多个内容是或的关系,匹配其中的一项就算匹配成功。
比如,我们使用 [World]
,W
可以匹配、o
也可以匹配,也就是说,W
、o
、r
、l
和 d
它们是或的关系,匹配其中的一项就行,比如我们来验证老伙伴 Hello World
,高亮部分就是匹配内容:
注意一下,匹配结果中灰色的点可不是点号,而是空格,可能这个网站只是为了让空格显著一点儿~
常用的表达式如下:
符号 | 解释 |
---|---|
[World] |
匹配 W 、o 、r 、l 和 d ,可以替换成我们想要的任何字符 |
[0-9] |
匹配数字 |
[a-z] |
匹配小写字母,如果匹配大写字母,需要使用 [A-Z]
|
[\s\S] |
匹配所有,\s 匹配所有空白符,包括换行,\S 匹配非空白符,不包括换行 |
[\u4e00-\u9fa5] |
匹配所有汉字 |
既然有想匹配括号中的内容,自然也就有了不想匹配括号中的内容。如果我不想匹配 [World]
中的任何一个字符,怎么办?
简单,使用 ^
符号,在 []
中使用 ^
就是取反的意思,上面的内容就变成了 [^World]
,验证一下 Hello World
就是:
很多情况下,仅仅匹配字母肯定不够,怎么匹配大小写字母加数字呢?
加一起就行,[a-zA-Z0-9]
就表示匹配大小写字母和数字,那有没有更简单的写法呢?
还真有,\w
表示 [a-zA-z0-9_]
,不过比上面的内容多了一个下划线,问题不大。除此以外,\d
表示匹配所有数字,.
就厉害了,它匹配出换行符(\n
和 \r
)的任何单个字符,整理一下:
符号 | 解释 |
---|---|
[a-zA-Z0-9] |
匹配单个大小写字母和数字 |
\w |
等价于 [a-zA-Z0-9_] ,匹配单个字母数字和下划线 |
. |
匹配除换行符之外的任何一个单个字符 |
\d |
匹配任何单个数字 |
上面讲了这么多,不知道大家有没有发现,我们去查看匹配结果的时候,发现一次只能匹配一个值,比如我用 [am]
去匹配 I am JiuXinDev
,发现结果是 a
和 m
,而不是 am
:
因为 []
只能匹配一个字符,下面我们再看如何匹配数量。
二、如何匹配数量
正则表达式常常用 {}
进行数量匹配,用法是:
符号 | 解释 |
---|---|
{n} |
匹配 n 次,例如 a{2} 可以匹配 aa ,但是不能匹配 a
|
{n,m} |
匹配 n 到 m 次,例如 a{1,2} 既可以匹配 aa ,也可以匹配 a
|
{n,} |
匹配至少 n 次,也就是 n 到正无穷,例如 a{2,} ,除了单个 a ,其他多少个连续 a 都可以匹配 |
这个时候,我们就可以匹配 I am JiuXinDev
中的 am
了,如果你仅仅想匹配 am
这个单词,文本字符 am
就可以搞定:
啊,这也太简单了,如果我们还想匹配 mac
中的 ma
呢,文本字符 am
就没有作用了,可以再加一个匹配项,中间使用 |
隔开,像这样 ma|am
。
但再匹配 aa
和 mm
呢?我们刚刚学的 {}
就派上了用场,可以通过正则表达式 [am]{2}
去匹配 am
和 ma
。
来做个练习
题目1:怎么从一个句子中找出 2-5 个字母的单词?提醒一下,单词与空格之间的边界可以用
\b
表示
字母对应着 [a-zA-Z]
,数量限制用 {n,m}
,2-5个单词的限制对应着 {2,5}
,加上边界,就是 \b[a-zA-Z]{2,5}\b
:
限定多少次才能匹配成功的叫做限定符,包括上面介绍的 {}
系列,还有一些简单版本的限定符:
符号 | 解释 |
---|---|
? |
匹配0或1次,等价于{0,1}
|
+ |
匹配1到无穷次,等价于 {1,}
|
* |
匹配0到无穷次,等价于 {0,}
|
获取标签内容是常有的事儿
题目2:给定
<groupId>QDReader.QDAarCenter</groupId>
, 如何提取<groupId>
和</groupId>
?
还记得上面介绍过的万能匹配符 .
吗?我们只要保证必须以 <
开头 和 >
结尾,可以写成 <.*>
:
这结果不对啊,并不是我想要的匹配标签
别急,再加个 ?
,写成 <.*?>
:
一个 ?
,涉及到了一个贪婪和非贪婪的知识,简单来说:
- 贪婪:尽可能多的匹配
- 非贪婪匹配:尽可能少的匹配
这么说还有点似懂非懂,拿上面的例子来讲,<groupId>
和 <groupId>QDReader.QDAarCenter</groupId>
都以 <
开头,以 >
结尾,如果是贪婪匹配,它的匹配结果是 <groupId>QDReader.QDAarCenter</groupId>
,贪婪嘛,能匹配多的,绝不匹配少的;如果是非贪婪匹配,它有两个匹配结果,<groupId>
和 </groupId>
。
本质上,+
和 *
都是贪婪匹配,?
则是非贪婪匹配。
三、如何匹配位置
定位符通常由下面这几种:
符号 | 解释 |
---|---|
^ |
匹配以指定字符串开始的位置,可以指定一行文本或者整段文本以指定字符开始 |
$ |
匹配以指定字符串结束的位置,可以指定一行文本或者整段文本以指定字符结束 |
\b |
单词边界,字与空格的位置 |
\B |
非单词边界匹配,指字符与字符之间的边界 |
\b
在上文已经用过,可以定位一个单词的开始和结束。^
用在 []
中进行字符匹配的时候,是取反的意思,我们也可以用来定位。
重回本文开头的 maven-metadata.xml 文件:
题目3:如何获取以
<version>
开头的该行所有内容?
从上面的 maven-metadata.xml 可以看出,<version>
标签前其实是有若干空格符号的,可以用 \s*
表示,结合定位符 ^
,整个正则表达式可以写成 ^\s*<version>.*
:
上面的解决方法还可以使用 $
解决,即匹配以 </version>
结尾的所有行,正则表达式为 .*</version>$
。
在一些情况下,^
和 $
还可以放在一起使用,例如 ^JiuXin$
就可以用来表示匹配只有内容为 JiuXin
的行。
四、如何匹配多内容
多个选项的匹配一般使用选择来完成。
1. 圆括号
敲黑板,选择中最重要的就是圆括号 ()
:
符号 | 解释 |
---|---|
() |
可以将所有的选项放在括号里面,用 | 隔开,匹配多个选项中的一项就算成功。() 会将分组捕获,() 内匹配到的每一个词都会被放入缓存,通过数字查看对应的缓存 |
这么听有点晕,我们挨个介绍一下。前半句听着有点像方括号 []
的意思,多个选项,匹配其中的一项就算完成匹配,不过它们的区别是:
-
[]
:每个选项是一个字符,也只能匹配一个字符。 -
()
:每个选项可以是一个字符,也可以是一个表达式,功能更强!
比如,(am|\d+)
就表示匹配 am
或者不定数数字量的字符串。
巩固一下:
题目四,从给定的语句中,匹配以 a 和 b 开头的单词?
提取单词用 \b
,以 a 开头的单词对应着 a[a-zA-Z]*
,匹配 a 或 b 可以用上面的选择表达式,结合一下就是 \b(a[a-zA-Z]*|b[a-zA-Z]*)\b
:
如果你注意到了匹配信息,会发现有点不一样的东西:
通常的匹配只有匹配结果 Match
,这里却多了显示信息 Group
,没错,它就是我们上面介绍的缓存,使用 \n
可以引用第 n 个缓存。
我们可以使用这个缓存做点不一样的事,比如 ([a-zA-Z])\1
表示匹配两个连续的字符。
巩固一下:
题目五:给定的语句中,匹配两个连续的单词。
表示一个单词可以用 \b\w+\b
,因为要使用缓存 \1
,所以得加上一个括号,组合在一起就是 (\b\w+\b)\s\1
:
使用缓存这种操作我们叫做反向引用。
2. 非捕获元
上面提到了,使用 ()
匹配的结果都会被缓存,而在括号中使用 ?:
就会消除缓存,非捕获元一共有如下几个:
符号 | 解释 |
---|---|
?: |
在选择 () 中使用,去除缓存 |
?= |
exp1(?=exp2) 匹配以 exp2 结尾的 exp1
|
?! |
exp1(?!exp2) 匹配不以 exp2 结尾的 exp1
|
?<= |
(?<=exp2)exp1 匹配以 exp2 开头的 exp1
|
?!= |
(?!=exp2)exp1 匹配不以 exp2 开头的 exp1
|
在介绍定位符的时候,我们介绍过 ^
和 $
,它们可以匹配整段和行开始和结束,也可以直接使用文本字符去匹配文本的开始和结束,但总觉得差点意思!
那这里的 ?=
和 ?<=
有什么不同呢?
非捕获元 ?<=
和 ?!=
,顾名思义,指的匹配部分以指定内容开头或者结束,匹配部分不包括指定内容。比如 (?<=he)\w+
匹配以 he
开头字符串,但匹配结果却不包含 he
:
还记得一开始的的题目吗?
题目6:如何获取 maven 库中最新库的版本信息?(获取
<latest>0.0.10</latest>
中间的0.0.10
)
有的版本号会包括字母和 -
,所以版本号正则可以概括成 [\w\.-]+
,加上以 <latest>
开头,可以写成 (?<=<latest>)[\w\.-]+
:
再来练习以什么结尾:
题目7:如何获取QQ邮箱里面的QQ账号?
首先,QQ邮箱一般以 qq.com
结尾,QQ账号一般5-11位数,第一个数字不是0(其他限制暂时没有想到),对应 [1-9]\d{4,10}
,两者结合就是 [1-9]\d{4,10}(?=@qq\.com)
:
到这里,我们应该可以看懂大部分的正则表达式了~
五、如何按优先级匹配
正则表达式从左往右计算,但是,它里面的各种符号也是有优先级的,由高到低如下:
符号 | 解释 |
---|---|
\ |
转义字符 |
() , (?:) , (?=) , []
|
圆括号和方括号 |
* , + , ? , {n} , {n,} , {n,m}
|
限定符 |
^ , $ , \ 任何元字符、任何字符 |
定位点和序列 |
| |
或操作 |
就跟我们写代码一样,运算符也有各种优先级,不过难度不大!
实操一下:
题目8:正则表达式
((\d)([a-zA-Z]{2}(\d)))\1\2\3\4
和字符串7ac87ac87ac88
可以匹配吗?如果可以,请输出它们的\1
、\2
、\3
和\4
。
这个正则表达式看着很复杂,好在它并没有使用 *
和 +
这两种限定符,所以匹配的结果的长度是一定的,之后我们按照优先级来看。
最外层是一个大括号,因为它是从左往右的第一个括号,所以它匹配的结果 \1
。
去除外层的括号,是 (\d)([a-zA-Z]{2}(\d))
,还是从左往右,接下来先匹配左边的 (\d)
,所以它是 \2
。
去除刚刚左边的 (\d)
,还有 ([a-zA-Z]{2}(\d))
,最外层又是一个括号,跟第一种情况一样,它就是 \3
。
最后还剩一个右边的 (\d)
,它是 \4
。
仔细看一下整个表达式 (\d)([a-zA-Z]{2}(\d))
,两边两个数字,中间是两个字母,一共四个数。\1
等于整个表达式,\2
+ \3
等于 \1
,\4
是整个表达式最后一个数字。
所以最终结果是匹配的,匹配部分看上图就好。
下期再见
不会正则的时候,总觉得正则难,一顿操作下来,发现正则也就那么多东西。
希望看完这篇文章的你,也能很快掌握正则!
感谢阅读,下期再见 👋
参考文章: