看nginx配置的时候,写表单验证的时候,都会遇到正则表达式。就是用一次看一次,看一次忘一次。为了让自己能从下一次的遗忘中快速回想起来,所以咱就费费键盘写一下🤣
👀本文是总结后的产物,主要参考https://juejin.cn/post/6844903845227659271
1. 正则总表
下表只是为了方便查询。正文从2. 从字符出发开始哦😊
描述 | 正则表达式 | 记忆方式/举例 |
---|---|---|
换行符 | \\n |
new line |
换页符 | \\f |
form feed |
回车符 | \\r |
return |
空白符 | \\s |
space |
制表符 | \\t |
tab |
垂直制表符 | \\v |
vertical tab |
回退符 | [\\b] |
backspace,之所以使用[] 符号是避免和 \\b 重复 |
除了换行符之外的任何字符 | . |
句号,除了句子结束符 |
单个数字, [0-9] | \\d |
digit |
除了[0-9] | \\D |
not digit |
包括下划线在内的单个字符,[A-Za-z0-9_] | \\w |
word |
非单字字符 | \\W |
not word |
匹配空白字符,包括空格、制表符、换页符和换行符 | \\s |
space |
匹配非空白字符 | \\S |
not space |
集合 | [] |
[123] 只会匹配1, 2, 3,其余字符不会被匹配到 |
连字符 | - |
/[1-9]/ 表示匹配1, 2, 3, …, 9 |
连字符 | - |
/[1-9]/ 表示匹配1, 2, 3, …, 9 |
匹配0或1次 | ? |
|
匹配0次或无数次 | * |
|
匹配1次或无数次 | + |
|
匹配1次或无数次 | + |
|
匹配x次 | {x} |
|
匹配介于min次到max次之间 | {min, max} |
|
匹配至少min次 | {min, } |
|
匹配至多max次 | {0, max} |
|
单词边界 | \\b |
boundary |
非单词边界 | \\B |
not boundary |
字符串开头 | ^ |
小头尖尖那么大个 |
字符串结尾 | $ |
终结者,美国科幻电影,美元符$ |
多行模式 | m标志 |
multiple of lines |
忽略大小写 | i标志 |
ignore case, case-insensitive |
全局模式 | g标志 |
global |
与 | 无 |
|
非 | [^regex] 和 ! |
|
或 | | |
2. 从字符出发
咱把正则表达式看成一个肉夹馍,两层饼皮一层肉。肉这部分正是整个正则的精华,它由字符和元字符构成:
- 字符:就是基础的计算机字符,通常正则表达式里使用的就是数字、英文字母。
- 元字符:特殊字符。是一些用来表示特殊语义的字符,比如
^
表示非,|
表示或; - 【❗注意】如果想匹配
^
这个符号,就需要使用\
进行转义,写成\^
。
2.1. 单个字符
单个字符
是指正则中的一个字符匹配到目标字符串的一个字符。这句话有点绕,但可以先看下去,结合后边的
多个字符
理解。
特殊字符 | 正则表达式 | 记忆方式 |
---|---|---|
换行符 | \\n |
new line |
换页符 | \\f |
form feed |
回车符 | \\r |
return |
空白符 | \\s |
space |
制表符 | \\t |
tab |
垂直制表符 | \\v |
vertical tab |
回退符 | [\\b] |
backspace,之所以使用[] 符号是避免和 \\b 重复 |
2.2. 多个字符
多个字符
是指正则中的多个字符匹配到目标字符串的一类字符,但这一类字符是单个进行匹配的。
2.2.1. []
:集合
🌰栗子**/[123]/**
:
2.2.2. -
:连字符
如果我想匹配所有的数字怎么办呢?从0写到9显然太过低效,所以元字符-
就可以用来表示区间范围
🌰栗子**/[1-9]/**
:
只会匹配1-9,包括0的其余字符不会被匹配到👇
2.2.3. ❗注意
2.2.3.1. []
中的^
:🌰/[^ab]/
^
表示一行开头 (后边会介绍到),但是在[]
中表示取反
:
/[ab]/
:a或者b;/[^ab]/
:啥都行,只要不是a或b(anythings except a and b),相当于取反。
2.2.3.2. -
是[]
中第一个字符时:🌰/[-.]/
比如/[-.]/
的含义是连字符-
或者点符.
。 但是,如果当连字符不是第一个字符时,比如[a-z],这就表示是从字母a到字符z。
2.2.4. 特殊字符表
匹配区间 | 正则表达式 | 记忆方式 |
---|---|---|
除了换行符之外的任何字符 | . |
句号,除了句子结束符 |
单个数字, [0-9] | \\d |
digit |
除了[0-9] | \\D |
not digit |
包括下划线在内的单个字符,[A-Za-z0-9_] | \\w |
word |
非单字字符 | \\W |
not word |
匹配空白字符,包括空格、制表符、换页符和换行符 | \\s |
space |
匹配非空白字符 | \\S |
not space |
3. 次数匹配
一对一和一对多的字符匹配都讲完了。如果要匹配4次a
,可以写成/aaaa/
。但如果要匹配20次,写20个a
就非常麻烦,所以需要使用正则的次数匹配。
3.1. ?
:0|1次
元字符?
代表了匹配一个字符或0个字符。
🌰栗子:匹配color和colour这两个单词
设想一下,如果你要匹配color和colour这两个单词,就需要同时保证u这个字符是否出现都能被匹配到。所以你的正则表达式应该是这样的:/colou?r/
。
3.2. *
: >=0次
元字符*
用来表示匹配0个字符或无数个字符。通常用来过滤某些可有可无的字符串。
3.3. +
: >=1次
元字符+
适用于要匹配同个字符出现1次或多次的情况。
3.4. {}
: 特定次数
在某些情况下,我们需要匹配特定的重复次数,元字符{}
用来给重复匹配设置精确的区间范围。
{x}
: x次;{min, max}
: 介于min次到max次之间;{min, }
: 至少min次;{0, max}
: 至多max次。
🌰栗子:匹配3次a
如’a’我想匹配3次,那么我就使用/a{3}/
这个正则,或者说’a’我想匹配至少两次就是用/a{2,}/
这个正则。
4. 位置边界
4.1. \b
:单词边界
边界正则表达式\b
,其中b是boundary的首字母。在正则引擎里它其实匹配的是能构成单词的字符(\w
)和不能构成单词的字符(\W
)中间的那个位置。
🌰栗子:找出特定单词
找到The cat scattered his food all over the room.
中的单词cat
👇:
我想找到cat这个单词,但是如果只是使用/cat/
这个正则,就会同时匹配到cat和scattered这两处文本。这时候我们就需要使用边界正则表达式\b
。
改写成/\bcat\b/
这样就能匹配到cat这个单词了。
4.2. ^
和$
:字符串边界
- 元字符
^
:用来匹配字符串的开头; - 元字符
$
:用来匹配字符串的末尾; - ❗注意:在长文本里,如果要排除换行符的干扰,我们要使用多行模式。
4.3. i
、m
和g
:模式匹配
- 元字符
i
:忽略大小写; - 元字符
m
:多行模式; - 元字符
g
:找到所有符合的匹配.
🌰栗子:长文本排除换行符的干扰
试着匹配I am scq000
这个句子:
I am scq000.
I am scq000.
I am scq000.
我们可以使用/^I am scq000\.$/m
这样的正则表达式,其实m
是multiple line的首字母。正则里面的模式除了m
外比较常用的还有i
和g
。前者的意思是忽略大小写,后者的意思是找到所有符合的匹配。
4.4. 小结
边界和标志 | 正则表达式 | 记忆方式 |
---|---|---|
单词边界 | \\b |
boundary |
非单词边界 | \\B |
not boundary |
字符串开头 | ^ |
小头尖尖那么大个 |
字符串结尾 | $ |
终结者,美国科幻电影,美元符$ |
多行模式 | m标志 |
multiple of lines |
忽略大小写 | i标志 |
ignore case, case-insensitive |
全局模式 | g标志 |
global |
5. 子表达式
字符匹配我们介绍的差不多了,更加高级的用法就得用到子表达式了。通过嵌套递归和自身引用可以让正则发挥更强大的功能。
从简单到复杂的正则表达式演变通常要采用分组、回溯引用和逻辑处理的思想。利用这三种规则,可以推演出无限复杂的正则表达式。
5.1. ()
:分组
其中分组体现在:所有以(
和)
元字符所包含的正则表达式被分为一组,每一个分组都是一个子表达式,它也是构成高级正则表达式的基础。如果只是使用简单的(regex)
匹配语法本质上和不分组是一样的,如果要发挥它强大的作用,往往要结合回溯引用的方式。
5.2. 回溯引用
所谓回溯引用(backreference)指的是模式的后面部分引用前面已经匹配到的子字符串。你可以把它想象成是变量,回溯引用的语法像\1,\2,....
,其中\1
表示引用的第一个子表达式,\2
表示引用的第二个子表达式,以此类推。而\0
则表示整个表达式。
5.2.1. 🌰栗子:匹配两个连续相同的单词
找到Hello what what is the first thing, and I am am scq000.
中的`两个连续相同的单词👇:
利用回溯引用,我们可以很容易地写出/\b(\w+)\s\1/
这样的正则。
5.2.2. 替换字符串
回溯引用在替换字符串中十分常用,语法上有些许区别,用$1,$2...
来引用要被替换的字符串。下面以js代码作演示:
var str = 'abc abc 123';
str.replace(/(ab)c/g,'$1g');
// 得到结果 'abg abg 123'
5.2.3. (?:regex)
:非捕获正则
如果我们不想子表达式被引用,可以使用非捕获正则(?:regex)
这样就可以避免浪费内存。
var str = 'scq000'.
str.replace(/(scq00)(?:0)/, '$1,$2')
// 返回scq00,$2
// 由于使用了非捕获正则,所以第二个引用没有值,这里直接替换为$2
5.3. 回溯引用的适用范围
有时,我们需要限制回溯引用的适用范围。那么通过前向查找和后向查找就可以达到这个目的。
5.3.1. (?=regex)
:前向查找
前向查找(lookahead)是用来限制后缀的。凡是以(?=regex)
包含的子表达式在匹配过程中都会用来限制前面的表达式的匹配。
🌰栗子:**happy happily**
两个单词:
- 我想获得以happ开头的副词,那么就可以使用
happ(?=ily)
来匹配。 - 如果我想过滤所有以happ开头的副词,那么也可以采用负前向查找的正则
happ(?!ily)
,就会匹配到happy单词的happ前缀。
5.3.2. (?<=regex)
:后向查找
后向查找(lookbehind)是通过指定一个子表达式,然后从符合这个子表达式的位置出发开始查找符合规则的字串。
🌰栗子:apple和people都包含ple这个后缀,那么如果我只想找到apple的ple,该怎么做呢?
我们可以通过限制app这个前缀,就能唯一确定ple这个单词了:/(?<=app)ple/
其中(?<=regex)
的语法就是我们这里要介绍的后向查找。regex指代的子表达式会作为限制项进行匹配,匹配到这个子表达式后,就会继续向后查找。
另外一种限制匹配是利用(?<!regex)
语法,这里称为负后向查找。与正前向查找不同的是,被指定的子表达式不能被匹配到。于是,在上面的例子中,如果想要查找apple的ple也可以这么写成/(?<!peo)ple/
。
5.3.3. 小结
回溯查找 | 正则 | 记忆方式 |
---|---|---|
引用 | \\0,\\1,\\2 和 $0, $1, $2 |
转义+数字 |
非捕获组 | (?:) |
引用表达式(()), 本身不被消费(?),引用(:) |
前向查找 | (?=) |
引用子表达式(()),本身不被消费(?), 正向的查找(=) |
前向负查找 | (?!) |
引用子表达式(()),本身不被消费(?), 负向的查找(!) |
后向查找 | (?<=) |
引用子表达式(()),本身不被消费(?), 后向的(<,开口往后),正的查找(=) |
后向负查找 | (?<!) |
引用子表达式(()),本身不被消费(?), 后向的(<,开口往后),负的查找(!) |
6. 与或非:逻辑处理
6.1. 与
在正则里面,默认的正则规则都是与的关系所以这里不讨论。
6.2. [^regex]
和!
:非
而非关系,分为两种情况:一种是字符匹配,另一种是子表达式匹配。
在字符匹配的时候,需要使用^
这个元字符。
在这里要着重记忆一下:只有在[
和]
内部使用的^
才表示非的关系。子表达式匹配的非关系就要用到前面介绍的前向负查找子表达式(?!regex)
或后向负查找子表达式(?<!regex)
。
6.3. |
:或
或关系,通常给子表达式进行归类使用。比如,我同时匹配a,b两种情况就可以使用(a|b)
这样的子表达式。
6.4. 小结
逻辑关系 | 正则元字符 |
---|---|
与 | 无 |
非 | [^regex] 和 ! |
或 | | |
7. 在JS中的应用
7.1. reg.test()
正则表达式本身有一个test的方法,这个方法只能测试是否包含,返回一个bool变量。
const r = /\d{3}/;
const a = '123';
const b = '123abc';
const c = 'abc';
r.test(a); // true
r.test(b); // true
r.test(c); // false
7.2. str.match()
与test()
不同,不只是返回bool变量,它会返回你所匹配到的内容。
const r = /compus/;
const reg = /m+/
const s = 'compus, I know something about you';
r.test(s); // true
s.match(r); // ['compus', index: 0, input: 'compus, I know something about you', groups: undefined]
s.match(reg); // ['m', index: 2, input: 'compus, I know something about you', groups: undefined]
等等,好像有点问题,明明something中也有一个m,为什么最后一个返回的是[“m”]?这不科学。
好吧,实际上,match()返回了第一个可以匹配的序列。想要实现之前的效果,就要用到前边介绍的模式匹配:
const reg = /m+/g
const s = 'compus, I know something about you';
s.match(reg); // ['m', 'm']
但是还有一个问题,上边说到分组,那么match会返回分组吗?
var str = "Here is a Phone Number 111-2313 and 133-2311"
var sr = /(\d{3})[-.]\d{4}/
var srg = /(\d{3})[-.]\d{4}/g
console.log(str.match(sr)); // ['111-2313', '111', index: 23, input: 'Here is a Phone Number 111-2313 and 133-2311', groups: undefined]
console.log(str.match(srg)); // ['111-2313', '133-2311']
所以结论是: 当使用了全局模式g
的时候,不会返回分组,而是全部的匹配结果;如果没有使用g
,会将匹配到的结果和分组以数组的形式返回。
7.3. reg.exec()
从字面意思来看,正则表达式的执行方法。 这个方法可以实现匹配全局,并返回分组的结果。reg.exec()
每次调用,返回一个匹配的结果,匹配结果和分组以数组的形式返回,不断的调用即可返回下一个结果,直到返回null
.
var str = "Here is a Phone Number 111-2313 and 133-2311" ;
var srg = /(\d{3})[-.]\d{4}/g;
var result = srg.exec(str);
while(result !== null) {
console.log(result);
result = srg.exec(str);
}
运行结果:
7.4. str.split()
现在来到了更强的功能上,先说下split,我们知道split是将字符串按照某个字符分隔开,比如有以下一段话,需要将其分割成单词。
var s = "unicorns and rainbows And, Cupcakes"
分割成单词,首先想到的是空格隔开,于是可以用下面方式实现:
var result = s.split(' ');
var result1 = s.split(/\s/);
//完全一样的效果
//["unicorns", "and", "rainbows", "And,", "Cupcakes"]
嗯,这样体现不出来正则的强大,而且最主要的是没有实现要求。因为还有一个”And,”。所以要用正则了,匹配条件是逗号或者空格:
result = s.split(/[,\s]/); // ["unicorns", "and", "rainbows", "And", "", "Cupcakes"]
结果仍然和需要的有出入,因为多了一个””。 我们并不是想让它分割的依据是逗号或者空格,依据应该是逗号或空格所在的连续序列。 在原来的基础上加一个+
,改成/[,\s]+/
:
result = s.split(/[,\s]+/);
// ["unicorns", "and", "rainbows", "And", "Cupcakes"]
7.4.1. 单词分割
好了,拓展一下,实现一个段落的单词分割,一个正则表达式就是
result = s.split(/[,.!?\s]+/)
当然,有个最简单的方法,我们可以这样去做
result = s.split(/\W+/);
接着,如果我们想将一个段落的句子都分隔开,一个可以实现的表达式就是
result = s.split(/[.,!?]+/)
最后,有一个小需求,就是在分割句子的同时,还想把相应的分隔符保留下来。
var s =
"Hello,My name is Vincent. Nice to Meet you!What's your name? Haha."
这是一个小小的ponit,记住如果想要保留分隔符,只要给匹配的内容分组即可
var result = s.split(/([.,!?]+)/)
//["Hello", ",", "My name is Vincent", ".", " Nice to Meet you", "!", "What's your name", "?", " Haha", ".", ""]
7.5. str.replace()
replace也是字符串的方法,它的基本用法是str.replace(reg,replace|function)
,第一个参数是正则表达式,代表匹配的内容,第二个参数是替换的字符串或者一个回掉函数。
注意,replace不会修改原字符串,只是返回一个修改后的字符串。除此外,正则表达式如果没有使用**g**
标志,也和match一样,只匹配或替换第一个。
7.5.1. 最简单的替换
// 🌰替换一个序列中的元音字母(aeiou),将其替换成一个double。 比如x->xx
var s = "Hello,My name is Vincent."
var result = s.replace(/([aeiou])/g,"$1$1")
//"Heelloo,My naamee iis Viinceent."
注意❗:
- 第二个参数必须是字符串;
- 注意不要忘记加
g
.
7.5.2. 第二个参数传function
先看一个最简单的示例:
var s = "Hello,My name is Vincent. What is your name?"
var newStr = s.replace(/\b\w{4}\b/g,replacer)
console.log(newStr)
function replacer(match) {
console.log(match);
return match.toUpperCase();
}
/*
name
What
your
name
Hello,My NAME is Vincent. WHAT is YOUR NAME?
*/
所以,函数的参数是匹配到的内容,返回的是需要替换的内容。好了,基本示例解释了基本用法,那么之前讨论的分组怎么办?如何实现分组呢?
//分组
function replacer(match,group1,group2) {
console.log(group1);
console.log(group2);
}
如果正则表达式分组处理,那么在回调函数中,函数的第二个、第三参数就是group1,group2。这样子,就可以做很多神奇的事情.
7.5.3. 综合练习
判断一个字符串中出现次数最多的字符,并统计次数。
var s = 'aaabbbcccaaabbbaaa';
var a = s.split('').sort().join(""); //"aaaaaaaaabbbbbbccc"
var ans = a.match(/(\w)\1+/g);
ans.sort(function(a,b) {
return a.length - b.length;
})
console.log('ans is : ' + ans[ans.length-1])