正则表达式
本文正则表达式基于javascript,不同的计算机语言对正则表达式的支持情况以及实现,语法不尽相同,不一定适用于其他语言。
简介
正则表达式,是一种文本模式(Regular Expression),是对字符串的一种匹配查找规则。 可以方便的在某一文本字符串中,查找、定位、替换符合某种规则的字符串。
比如说,我们想要找出一段文本中的手机号码,文本内容如下:
name:Mark tel:13800138000
name:Jhon tel:13800138888很明显,在这段文本中,手机号码是以 tel:开头,这符合一定的规则,这样我们可以通过正则表达式来书写这个规则, 然后去查找匹配:
let text = `name:Mark tel:13800138000
name:Jhon tel:13800138888`
let result = text.match(/tel:(1\d{10})/)
// ["tel:13800138000", "13800138000", index: 0, input: "tel:13800138000", groups: undefined]
let tel = result[1]
// 13800138000/tel:(1\d{10})/ 便是所说的正则表达式。
RegExp 与字面量
在javascript中,我们可以使用构造函数RegExp 创建正则表达式。
new RegExp(pattern[, flags])
let regExp = new RegExp('\\d', 'g')也可以通过 字面量的方式:
let regExp = /\d/g两种创建正则表达式适用的场景有些细微的不同,一般使用new RegExp()来创建动态的正则表达式,使用字面量创建静态的正则表达式。
正则表达式字面量是提供了对正则表达式的编译,当正则表达式保持不变时,用字面量的方式创建正则表达式可以获得更好的性能。
以下讨论以正则表达式字面量来创建正则表达式:
正则表达式一般由元字符和普通字符组成。
元字符
元字符也叫特殊字符,是正则表达式规定的,对符合特定的单一的规则的字符的描述。
| 字符 | 含义 |
|---|---|
| \ | 在非特殊字符的前面加反斜杠,表示这个字符是特殊的,不能从字面上解释。比如在\d描述的不是一个普通的字符d,而是正则表达式中的数值0-9。如果在特殊字符前面加反斜杠,这表示将这个字符转义为普通字符,比如 ?在正则中有其特殊含义,前面加反斜杠?,这可以将其转为普通的?。 |
| ^ | 匹配文本开始的位置,如果开启了多行标志,也会匹配换行符后紧跟的位置。 比如 ^a会匹配abc,但不会匹配到bac。 |
| $ | 匹配文本结束的位置,如果开启了多行标志,也会匹配换行符前紧跟的位置。 比如 b$会匹配acb,但不会匹配到abc。 |
| * | 匹配前一个表达式0次到多次。 比如, ab*会匹配到abbbbbbc中的abbbbbb,以及acbbbbb中的a。 |
| + | 匹配前一个表达式1次到多次。 比如, ab+会匹配到abbbbbbc中的abbbbbb,但不会匹配acbbbbb。 |
| ? | 匹配前一个表达式0次到1次。 比如, ab*会匹配到abbbbbbc中的ab,以及acbbbbb中的a。 |
| . | 匹配除换行符之外的任何单个字符。 |
| x|y | 匹配 x或者y。 |
| [xyz] | 表示一个字符的集合。匹配集合中的任意一个字符。可以使用破折号-来指定一个字符范围。比如, [0-4]和[01234],都可以匹配4567中的4。 |
| [^xyz] | 表示一个方向字符集合。匹配任意一个不包括在集合中的字符。可以使用破折号-来指定一个字符范围。比如, [0-4]和[01234],都可以匹配2345中的5。 |
| {n} | n为一个整数,表示匹配前一个匹配项n次。 比如 a{2}不会匹配abc中的a,但会匹配aaaabc中的aa。 |
| {m,n} | m,n都是一个整数,匹配前一个匹配项至少发生了m次,最多发生了n次。 当m,n值为0时,这个值被忽略,当n值不写,如 {1,}表示1次到多次。当m值不写时,如{,1}表示0次到1次。 |
| (x) | 匹配x并且捕获该匹配项。称为捕获括号,括号中的匹配项也称作子表达式。 |
| (?:x) | 匹配x但不捕获该匹配项。称为非捕获括号。 |
| x(?=y) | 匹配x且当x后面跟着y。称为正向肯定查找(正向前瞻)。 |
| x(?!y) | 匹配x且当x后面不跟着y。称为正向否定查找(负向前瞻)。 |
| [\b] | 匹配一个退格(U+0008)。 |
| \b | 匹配一个词的边界。匹配的值的边界并不包含在匹配的内容中。 |
| \B | 匹配一个非单词的边界。 |
| \d | 匹配一个数字。等价于[0-9]。 |
| \D | 匹配一个非数字。等价于[^0-9]。 |
| \n | 匹配一个换行符 (U+000A)。 |
| \r | 匹配一个回车符 (U+000D)。 |
| \s | 匹配一个空白字符,包括空格、制表符、换页符和换行符。 等价于 [ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] |
| \S | 匹配一个非空白字符。 |
| \t | 匹配一个水平制表符 (U+0009)。 |
| \w | 匹配一个单字字符(字母、数字或者下划线)。 等价于 [A-Za-z0-9_]。 |
| \W | 匹配一个非单字字符。 |
| \xhh | 与代码 hh 匹配字符(两个十六进制数字) |
| \uhhhh | 与代码 hhhh 匹配字符(四个十六进制数字)。 |
上表在多数文章都会提及,但有一些注意的细节,下面我单独拎出来说说。
[xyz]匹配集合中的任意一个字符
这个字符集的元素,可以是普通字符,也可以是特殊字符,也可以用破折号-规定一个字符集范围。
以匹配数字为例,可以写成[0123456789],也可以写成[\d],也可以写成[0-9]。
类似于()等特殊字符,在[]中有其作用,都特殊字符的作用一致,不能直接当做普通字符来使用,所以我们需要使用反斜杠\将其转义为普通字符,值得注意的是,上表的特殊字符中,星号*、小数点.在[]中并没有特殊用途,所以不需要做转义处理,当然,即使做了转义,也不会出现问题;而破折号-在[]中有其特殊作用,所以作为普通字符使用时,需要转义。?:匹配前一个表达式0次到1次。
其实这里准确描述来说,匹配前一个表达式,且该表达式 非任何量词*、+、?或{},匹配前一个表达式0次到1次。
如果紧跟在 非任何量词*、+、?或{}的后面,将会使量词变为非贪婪的(匹配尽量少的字符)
贪婪与非贪婪匹配,我们在下文细说。
等价字符
正则表达式中,有不少特殊字符的写法,是等价的,也可以说是简写形式,下表的左右两边,都是等价的。
| regExp | regExp |
|---|---|
| * | {0,} |
| + | {1,} |
| ? | {0,1} |
| \d | [0-9] |
| \D | [^0-9] |
| \w | [a-zA-Z*] |
| \W | [^a-zA-Z*] |
| \s | [\f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] |
贪婪模式与非贪婪
什么是贪婪模式?
贪婪是指正则表达式匹配时,是贪心的,会尽可能的匹配多的字符,主要体现在量词特殊字符:
// 匹配一个到多个数字
let r = /\d+/
let t1 = '12a'
let t2 = '1234a'
let t3 = 'a12b345'
console.log(t1.match(r)[0]) // 12
console.log(t2.match(r)[0]) // 1234
console.log(t3.match(r)[0]) // 12非贪婪,即是让正则表达式匹配尽量少的字符。那么如何改变正则表达式的贪婪模式?
在量词特殊字符后面紧跟使用?
我们说说的量词包括*, +, ?, {m,n}。那么紧跟了?,会有什么不同的表现呢?
我们从例子来分析:
let r1 = /<div>.*<\/div>/
let r2 = /<div>.*?<\/div>/
let str = '<div>aaa</div>bbb<div></div>ccc'变量r1是贪婪匹配,得到的结果会是什么呢?
console.log(str.match(r1)[0])
// <div>aaa</div>bbb<div></div>在这段字符串中,有两个</div>的匹配字符串,正则表达式在遇到第一个</div>匹配字符项时,同时满足了/.*/和/<\/div>/的匹配条件,优先作为/.*/的匹配值,在遇到第二个时,同样还是优先作为/.*/的匹配值,直到匹配的字符串str的结束,没有满足条件的匹配字符串,再把第二个</div>作为/<\/div>/的匹配值。最终得到了<div>aaa</div>bbb<div></div>的匹配结果。
变量r2这是非贪婪匹配,得到的结果又会有所不同:
console.log(str.match(r1)[0])
// <div>aaa</div>同样,两个</div>的匹配字符串,但实际非贪婪匹配模式,在匹配到第一个</div>,就不会再继续向下匹配字符串了。
也就是说,贪婪匹配是,在满足规则下,尽可能多的匹配更多的字符串,直到字符串结束或没有满足规则的字符串了;非贪婪匹配是,在满足规则下,尽可能少的匹配最少的字符串,一旦得到满足规则的字符串,就不再向下匹配。
x*?:尽可能少的匹配x,匹配的结果可以是0个x;x+?:尽可能少的匹配x,但匹配的结果至少有1个x;x??:尽可能少的匹配x, 匹配的结果可以是0个x,但最多可以有一个x;x{m,n}?:尽可能少的匹配x,但匹配的结果至少有m个x,最多可以有n个x;
可能从字面来说,不好理解 x??, x{m,n}? ,来看一个例子就可以明白了:
let s1 = '<div>aa</div>'
let s2 = '<div>a</div>'
let s3 = '<div></div>'
let r1 = /<div>a?<\/div>/
console.log(r1.test(s1)) // false
console.log(r1.test(s2)) // true
console.log(r1.test(s3)) // truelet s1 = '<div>aaa</div>'
let s2 = '<div>aa</div>'
let s3 = '<div>aaaa</div>'
let r1 = /<div>a{2,3}<\/div>/
let r2 = /<div>a{2,3}?/
console.log(r1.test(s1)) // true
console.log(r1.test(s2)) // true
console.log(r1.test(s3)) // false
console.log(s1.match(r2)[0]) // <div>aa
console.log(s2.match(r2)[0]) // <div>aa
console.log(s3.match(r2)[0]) // <div>aa正则表达式标志
| 标志 | 描述 |
|---|---|
| g | 全局搜索 |
| i | 不区分大小写搜索 |
| m | 多行搜索 |
| y | 执行“粘性”搜索,匹配从目标字符串的当前位置开始 |
| u | Unicode模式。用来正确处理大于 \uFFFF 的Unicode字符 |
m
使用m标志时,会改变开始(^)和结束字符($)的工作模式,变为在多行上匹配,分别匹配每一行的开始和结束,即\n或\r 分割。
y
使用y标志时,匹配是从RegExp.lastIndex指定的位置开始匹配,匹配为真时,会修改 lastIndex的值到当前匹配字符串后的位置,下次匹配从这个位置开始匹配,如果匹配为假时,不会修改lastIndex的值。
let reg = /o/y
let str = 'foo'
// lastIndex 为 0,从字符 f 开始匹配
reg.test(str) // false
// 由于结果为 false, lastIndex 还是为 0
reg.test(str) // false
let str2 = 'oof'
// lastIndex 为 0 ,从字符 o 开始匹配
reg.test(str2) // true
// lastIndex 此时修改为 1, 从第二个 o 开始匹配
reg.test(str2) // true
// lastIndex 此时修改为 2
reg.test(str2) // false 此时开始匹配的字符是 f
// lastIndex没有被修改
reg.test(str2) // false正则表达式中的捕获—— \1,\2,\3... 以及 $1,$2,$3
在上文中我们介绍了 (x) 是匹配 x 并捕获,那么有了捕获就必然可以去使用捕获到的结果, \1,\2,\3... 以及$1,$2,$3... 便是指捕获的结果。
\1, \2, \3, \4, \5, \6, \7, \8, \9 在正则表达式中使用,捕获结果为正则表达式的源模式.
在这个正则表达式中(bc)被捕获并标记为\1, (ef)被捕获并标记为\2。
let reg = /a(bc)d(ef)/也可以使用来简化正则表达式
let reg = /a(bc)dbc/
let reg2 = /a(bc)d\1/
let str = 'abcdbc'
reg.test(str) // true
reg2.test(str) // true$1, $2, $3, $4, $5, $6, $7, $8, $9 是RegExp的包含括号子表达式的正则表达式静态的只读属性。
let reg = /a(bc)d/
let str = 'abcd'
reg.test(str)
console.log(RegExp.$1) // bc在 String.replace() 中使用:
let reg = /(\w+)\s(\w+)/
let str = 'apple pear'
str.replace(reg, '$2 $1') // pear apple
RegExp.$1 // apple
RegExp.$2 // pear