正则表达式

159 阅读9分钟

参考链接:deerchao.cn/tutorials/r…

入门 测试正则表达式 元字符 字符转义 重复 字符类 分枝条件 反义 分组 后向引用 零宽断言 负向零宽断言 注释 贪婪与懒惰 处理选项 平衡组/递归匹配

正则表达式的基本组成元素可以分为:字符和元字符

字符:就是基础的计算机字符编码,通常正则表达式里面使用的就是数字、英文字母

元字符:特殊字符,是一些用来表示特殊语义的字符。如^表示非,|表示或等。利用这些元字符

单个字符

一对一

要匹配特殊字符,就得请出我们第一个元字符**\** ,它是转义字符字符,顾名思义,就是让其后续的字符失去其本来的含义。

例子:

我想匹配*这个符号,由于*这个符号本身是个特殊字符,所以我要利用转义元字符\来让它失去其本来的含义
\*

字符串:apple*aple  
公式:\*
结果:*

特殊字符汇总

特殊字符正则表达式记忆方式
换行符\nnew line
换页符\fform feed
回车符\rreturn
空白符\sspace
制表符\ttab
垂直制表符\vvertical tab
回退符[\b]backspace,之所以使用[]符号是避免和\b重复

多个字符

一对多

只要引入集合区间和通配符的方式就可以实现一对多的匹配

集合的定义方式是使用中括号[]

字符串:1232132123445
公式:[123]
结果:每个123都能匹配到

匹配所有的数字:从0写到9显然太过低效,所以元字符-就可以用来表示区间范围

字符串:A13D21CD
公式:[0-9]
结果:匹配数字

匹配多个字符的简便正则表达式

匹配区间正则表达式记忆方式
除了换行符之外的任何字符.句号,除了句子结束符
单个数字, [0-9]\ddigit
除了[0-9]\Dnot digit
包括下划线在内的单个字符,[A-Za-z0-9_]\wword
非单字字符\Wnot word
匹配空白字符,包括空格、制表符、换页符和换行符\sspace
匹配非空白字符\Snot space
\W   !@#]^
\d  12321321
\D  asdA!@@#@!#

循环与重复

同时匹配多个字符

要实现多个字符的匹配我们只要多次循环,重复使用我们的之前的正则规则就可以了。那么根据循环次数的多与少,我们可以分为0次,1次,多次,特定次。

0 | 1

元字符?代表了匹配一个字符或0个字符。设想一下,如果你要匹配colorcolour这两个单词,就需要同时保证u这个字符是否出现都能被匹配到。所以你的正则表达式应该是这样的:/colou?r/

字符串:color   colour
公式:colou?
结果:colo	colou

字符串:tests 、tesa  
公式:test?
结果:test、tes

>= 0

元字符*用来表示匹配0个字符或无数个字符。通常用来过滤某些可有可无的字符串。

字符串:teststeass
公式:tes*a
结果:tea

>= 1

元字符+适用于要匹配同个字符出现1次或多次的情况。

字符串:teststeass
公式:s+te
结果:ste

特定次数

在某些情况下,我们需要匹配特定的重复次数,元字符{}用来给重复匹配设置精确的区间范围。如'a'我想匹配3次,那么我就使用/a{3}/这个正则,或者说'a'我想匹配至少两次就是用/a{2,}/这个正则。

以下是完整的语法:

- {x}: x次
- {min, max}: 介于min次到max次之间
- {min, }: 至少min次
- {0, max}: 至多max次
匹配规则元字符联想方式
0次或1次?,此事
0次或无数次*宇宙洪荒,辰宿列张:宇宙伊始,从无到有,最后星宿布满星空
1次或无数次+一加, +1
特定次数{x}, {min, max}可以想象成一个数轴,从一个点,到一个射线再到线段。min和max分别表示了左闭右闭区间的左界和右界

实践

{x}: x次
字符串:asdqwd1aaaaa2312354wesd12`3
公式:a{4}
结果:aaaa

{min, max}: 介于min次到max次之间
字符串:asdqwd1aaaaa2312354wesd12`3
公式:a{1,5}
结果:match1  a   match2 aaaaa

{min, }: 至少min次
字符串:asdqwd1aaaaa2312354wesd12`3
公式:w{1,}
结果:match1  w   match2 w

- {0, max}: 至多max次

位置边界

上面我们把字符的匹配都介绍完了,接着我们还需要位置边界的匹配。在长文本字符串查找过程中,我们常常需要限制查询的位置。比如我只想在单词的开头结尾查找。

单词边界

单词是构成句子和文章的基本单位,一个常见的使用场景是把文章或句子中的特定单词找出来。我想找到cat这个单词,但是如果只是使用/cat/这个正则,就会同时匹配到catscattered这两处文本。这时候我们就需要使用边界正则表达式\b,其中b是boundary的首字母。在正则引擎里它其实匹配的是能构成单词的字符(\w)和不能构成单词的字符(\W)中间的那个位置。

上面的例子改写成/\bcat\b/这样就能匹配到cat这个单词了。

字符串:The cat scattered his food all over the room.
公式:\bcat\b
结果:cat

字符串边界

匹配完单词,我们再来看一下一整个字符串的边界怎么匹配。==元字符^用来匹配字符串的开头。而元字符$用来匹配字符串的末尾。==注意的是在长文本里,如果要排除换行符的干扰,我们要使用多行模式。试着匹配I am scq000这个句子:

I am scq000.
I am scq000.
I am scq000.

我们可以使用/^I am scq000\.$/m这样的正则表达式,其实m是multiple line的首字母。正则里面的模式除了m外比较常用的还有i和g。前者的意思是忽略大小写,后者的意思是找到所有符合的匹配。

最后,总结一下:

边界和标志正则表达式记忆方式
单词边界\bboundary
非单词边界\Bnot boundary
字符串开头头尖尖那么大个
字符串结尾$终结者,美国科幻电影,美元符$
多行模式m标志multiple of lines
忽略大小写i标志ignore case, case-insensitive
全局模式g标志global

子表达式

字符匹配我们介绍的差不多了,更加高级的用法就得用到子表达式了。通过嵌套递归和自身引用可以让正则发挥更强大的功能。

从简单到复杂的正则表达式演变通常要采用分组、回溯引用和逻辑处理的思想。利用这三种规则,可以推演出无限复杂的正则表达式。

参考地址(侵删):

https://juejin.cn/post/6844903845227659271

好用的正则表达式工具

https://regex101.com/

分组

其中分组体现在:所有以()元字符所包含的正则表达式被分为一组,每一个分组都是一个子表达式,它也是构成高级正则表达式的基础。如果只是使用简单的(regex)匹配语法本质上和不分组是一样的,如果要发挥它强大的作用,往往要结合回溯引用的方式。

回溯引用

所谓回溯引用(backreference)指的是模式的后面部分引用前面已经匹配到的子字符串。你可以把它想象成是变量,回溯引用的语法像\1,\2,....,其中\1表示引用的第一个子表达式,\2表示引用的第二个子表达式,以此类推。而\0则表示整个表达式。

假设现在要在下面这个文本里匹配两个连续相同的单词,你要怎么做呢?

Hello what what is the first thing, and I am am scq000.

利用回溯引用,我们可以很容易地写出\b(\w+)\s\1这样的正则。

回溯引用在替换字符串中十分常用,语法上有些许区别,用$1,$2...来引用要被替换的字符串。下面以js代码作演示:

var str = 'abc abc 123';
str.replace(/(ab)c/g,'$1g');
// 得到结果 'abg abg 123'

如果我们不想子表达式被引用,可以使用非捕获正则(?:regex)这样就可以避免浪费内存。

var str = 'scq000'.
str.replace(/(scq00)(?:0)/, '$1,$2')
// 返回scq00,$2
// 由于使用了非捕获正则,所以第二个引用没有值,这里直接替换为$2

有时,我们需要限制回溯引用的适用范围。那么通过前向查找和后向查找就可以达到这个目的。

前向查找

前向查找(lookahead)是用来限制后缀的。凡是以(?=regex)包含的子表达式在匹配过程中都会用来限制前面的表达式的匹配。例如happy happily这两个单词,我想获得以happ开头的副词,那么就可以使用happ(?=ily)来匹配。如果我想过滤所有以happ开头的副词,那么也可以采用负前向查找的正则happ(?!ily),就会匹配到happy单词的happ前缀。

后向查找

介绍完前向查找,接着我们再来介绍一下它的反向操作:后向查找(lookbehind)。后向查找(lookbehind)是通过指定一个子表达式,然后从符合这个子表达式的位置出发开始查找符合规则的字串。举个简单的例子: applepeople都包含ple这个后缀,那么如果我只想找到appleple,该怎么做呢?我们可以通过限制app这个前缀,就能唯一确定ple这个单词了。

/(?<=app)ple/

其中(?<=regex)的语法就是我们这里要介绍的后向查找。regex指代的子表达式会作为限制项进行匹配,匹配到这个子表达式后,就会继续向查找。另外一种限制匹配是利用(?<!regex) 语法,这里称为负后向查找。与正前向查找不同的是,被指定的子表达式不能被匹配到。于是,在上面的例子中,如果想要查找appleple也可以这么写成/(?<!peo)ple

需要注意的,不是每种正则实现都支持后向查找。在javascript中是不支持的,所以如果有用到后向查找的情况,有一个思路是将字符串进行翻转,然后再使用前向查找,作完处理后再翻转回来。看一个简单的例子:

// 比如我想替换apple的ple为ply
var str = 'apple people';
str.split('').reverse().join('').replace(/elp(?=pa)/, 'ylp').split('').reverse().join('');

ps: 感谢评论区提醒,从es2018之后,chrome中的正则表达式也支持反向查找了。不过,在实际项目中还需要注意对旧浏览器的支持,以防线上出现Bug。详情请查看kangax.github.io/compat-tabl…

最后回顾一下这部分内容:

回溯查找正则记忆方式
引用\0,\1,\2 和 0,0, 1, $2转义+数字
非捕获组(?: )引用表达式(()), 本身不被消费(?),引用(: )
前向查找(?=)引用子表达式(()),本身不被消费(?), 正向的查找(=)
前向负查找(?!)引用子表达式(()),本身不被消费(?), 负向的查找(!)
后向查找(?<=)引用子表达式(()),本身不被消费(?), 后向的(<,开口往后),正的查找(=)
后向负查找(?<!)引用子表达式(()),本身不被消费(?), 后向的(<,开口往后),负的查找(!)

逻辑处理

计算机科学就是一门包含逻辑的科学。让我们回忆一下编程语言当中用到的三种逻辑关系,与或非。

在正则里面,默认的正则规则都是的关系所以这里不讨论。

关系,分为两种情况:一种是字符匹配,另一种是子表达式匹配。在字符匹配的时候,需要使用^这个元字符。在这里要着重记忆一下:只有在[]内部使用的^才表示非的关系。子表达式匹配的非关系就要用到前面介绍的前向负查找子表达式(?!regex)或后向负查找子表达式(?<!regex)

或关系,通常给子表达式进行归类使用。比如,我同时匹配a,b两种情况就可以使用(a|b)这样的子表达式。

逻辑关系正则元字符
[^regex]和!
|

总结

对于正则来说,符号之抽象往往让很多程序员却步。针对不好记忆的特点,我通过分类和联想的方式努力让其变得有意义。我们先从一对一的单字符,再到多对多的子字符串介绍,然后通过分组、回溯引用和逻辑处理的方式来构建高级的正则表达式。

在最后,出个常用的正则面试题吧:请写出一个正则来处理数字千分位,如12345替换为12,345。请尝试自己推理演绎得出答案,而不是依靠搜索引擎:)。

参考文件

好用的正则工具

参考地址(侵删):

https://juejin.cn/post/6844903845227659271
https://deerchao.cn/tutorials/regex/regex.htm