说在前面
正则表达式在查找、替换等方面非常强大,但是他的语法也比较难以掌握,总是出现学了忘,忘了学的情况。这不,最近又在学正则表达式了。之前在网上看到过很多正则表达式快速入门的文章,但是读来发现,篇幅还是太长,概念还是太多。读过之后,感觉脑子里涌入很多的东西,很混乱,当时理解了,后面又忘了,本质上讲还是没有理解透彻。
这次仔细梳理之后发现正则表达式其实蛮简单的,只要把知识以一个较为合理的方式梳理,是很容易消化吸收的。这篇文章秉承把好钢用在刀刃上,并没有覆盖正则表达式的方方面面,只覆盖了最常用的部分,保证读完之后对于正则表达式的使用有一个基本的理解,立马就能用起来解决实际问题。至于进阶用法,完全可以按需学习。
基础
我们先从一个具体的例子讲起,假如有下面这样一串字符:
xyz yyz
如何我们想查找yz
,查找yz
的正则表达式是什么呢?就是yz
,这么简单?就是这么简单!现在我们来具体看一下正则表达式的引擎是怎么利用我们给他的正则表达式去查找的:为了方便理解,我们先给每个字符编个号:
xyz yyz
0123456
这里的数字并不是在文本中真实存在的,只是标在这里,方便叙述。
现在引出第一个重要概念——正则表达式是逐字符匹配的。引擎拿到表达式yz
会先从第0号位置尝试匹配,结果发现文本里的x
和表达式里的y
匹配不上,再尝试从第1号位置匹配,发现文本里的y
和引擎里的y
能匹配上,继续尝试匹配下一位发现文本里的z
和表达式里的z
能匹配上,此时,表达式里的符号已经全部被匹配上了,匹配成功。以此类推,检查所有能匹配上的字符串。最终匹配的结果如下:
xyz yyz
加粗部分表示匹配的文本。
那如果我想同时匹配xyz
和yyz
呢?可以用[]
来实现,具体的正则表达式可以写成[xy]yz
,他表示我希望匹配到的字符串由3个字符组成,第一个字符要么是x
要么是y
,第二个字符是y
,第三个字符是z
。
还有一个与之类似的取反表达式^[]
,例如[^xy]yz
,表示希望匹配到一个3个字符的字符串,第一个字符不能是x
也不能是y
,其他字符都可以,第二个字符是y
,第三个字符是z
。
还有两个特殊的字符^
和$
,解决行首和行尾的匹配问题。实现这样一个实际的场景,如果我只想匹配行尾的yz
,我该如何做到呢?可以用yz$
实现,他表示我想匹配的那个字符串有两个字符,第一个字符是y
,第二个字符是z
,并且第二个字符后紧跟的是换行符(也就是在行尾)。行首也与之类似,例如^x
,表示我想匹配一个在行首的字符x
。
讲到这里,我们已经能用正则表达式做很多事情了。但还不够优雅,先把刚刚讲过的内容总结一下,下面再看如何更优雅的写正则表达式。
- 正则表达式最重要的原理——正则表达式是逐字符匹配的。
[]
和[^]
表达式- 字符
^
和$
特殊字符
这一部分主要是对[]
和[^]
的延申。试想,如果你希望当前位置的字符是0-9这10个数字里哪一个都行,那用[]
应该怎么写?好像应该写成这样[0123456789]
?嗯,确实是正确的,但是好像太啰嗦了,有没有更好的办法呢?
确实有,第一种简写方法[0-9]
,用-
表示一串字符,类似的还有a-z
表示26个小写字母,A-Z
表示26个大写字母。嗯,看起来已经很不错了,但是其实还有更简便的写法\d
,这是一个特殊字符,表示的就是[0-9]
。
像\d
这样的特殊字符,正则表达式中还有很多,具体如下:
\d
:表示0-9中的任一数字,也就是[0-9]
。\D
:表示除\d
以外的所有字符,也就是[^\d]
。\w
:表示A-Z
,a-z
,0-9
和_
总计63个字符,也就是[A-Za-z0-9_]
(注意最后一个字符_
是下划线)。\W
:表示除\w
以外的所有字符,也就是[^\w]
。\s
:所有空白字符,包括空格
、\t
,\n
,\r
,也就是[ \t\n\r]
(注意第一个字符是空格)。
\S
:所有除\s
以外的字符,也就是[^\s]
。.
:表示除换行符\n
以外的任意字符,可以写成[^\n]
。
量词
如果我们需要匹配5个字符x
,我们可以描述字符x
的个数吗?答案是可以的!这样我们就不用把表达式写成xxxxx
,直接x{5}
就可以描述了,是不是更优雅了呢 (●ˇ∀ˇ●) 除此之外,我们还可以描述一个范围,比如x{2,5}
表示至少有2个字符x
,至多有5个字符x
。
x{m}
:m
个字符x
。x{m,n}
:至少(包含)m
个字符x
,至多(包含)n
个字符x
。x*
:零个或多个字符x
。x+
:一个或多个字符x
。x?
:零个或一个字符x
。
分组
我们可以将多个字符用小括号括起来,作为一个分组。具体来说,主要有3个应用,我们逐个来看。
表达逻辑或
用例子来说明,比如有如下文本:
google.com
baidu.com
bilibili.com
youtube.com
如果我只想匹配,google.com
和baidu.com
,我应该怎么写呢?观察可以发现,他们最后四个字符都是.com
,不同之处就是baidu
和google
,其实就是一个逻辑或的关系。这个时候就可以用分组和或运算符|
来表示这个逻辑或的关系,(google|baidu)\.com
。他就表示,我要匹配的字符串最开始遇到的几个字符要么是google
要么是baidu
,然后紧跟着.com
四个字符。
与量词结合使用
之前我们了解到的量词只能描述这个量词前面紧贴着的那个字符出现多少次,比如xy+
中量词+
只能描述y
有一个或多个,而无法修饰x
,也就是只能匹配xy
和xyy
这些,但无法匹配xyxy
。但是,如果在分组后面加上量词,可以描述有多少个这样的分组。看个例子,(xy)+
就表示,有一个或多个xy
,他能匹配xyxy
、xyxyxy
等。
用于替换
假如我们有这样一个实际的任务,我现在有很多文件名:
10001.png
10002.png
10003.png
...
99999.png
如果我希望把每个文件名的编号前面都加上img
,那这样就需要我们捕获(capture)我们匹配到的字符串。\d{5}\.png
可以用来匹配每一个文件名,我们需要捕获的部分其实就是数字部分,也就是\d{5}
,我们可以用小括号把这一部分包裹起来,也就是(\d{5})\.png
。这样,这个分组内匹配到的字符串,就会被保存下来,供我们后面使用。替换文本写成img$1\.png
就可以引用我们刚才保存下来的字符串了。
分组也是可以嵌套的,例如(exp1(epx2)exp3)exp4
,此时如果想引用分组,他们的编号由左括号出现的顺序决定,第一个左括号内的表达式对应的文本的编号是$1
,第二个左括号内的表达式文本的编号是$2
。