前言
无论是写代码还是脚本,当我们要处理字符串或者提炼重要信息的时候,正则表达式都可以是我们的好帮手。
不过很多同学都有一种这样的感触,正则 = 天书 ,比如下面的邮箱表达式:
\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\.]+ 就可以获取到我想要的内容!
先分享给大家一个学习正则表达式的宝藏网站:
regex101
这个网站可以输入正则表达式和你想验证的文本,并将匹配的内容高亮,甚至会告诉你为什么会这么匹配,当然还有一些其他小技巧:
网站地址: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 就可以搞定:
image.png
啊,这也太简单了,如果我们还想匹配 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 是整个表达式最后一个数字。
匹配结果
所以最终结果是匹配的,匹配部分看上图就好。
下期再见
不会正则的时候,总觉得正则难,一顿操作下来,发现正则也就那么多东西。
希望看完这篇文章的你,也能很快掌握正则!
感谢阅读,下期再见 👋
参考文章:














网友评论