正则表达式教程

在很多编程语言中如javaPHPpythonJavaScript 等都可以使用正则表达式,但是不管在什么语言中,正则表达式的规则都是一样的。

正则表达式是独立的体系,与编程语言无关,唯一的关联就是不同语言中使用正则表达式规则的API不同。

正则表达式主要对字符串进行操作:查找替换

在下列场景中经常运用到正则表达式:

  • 表单数据的验证
  • 代码高亮
  • 爬虫

那么,接下来开始了解正则表达式,由于本人是前端开发出身,所以语法示例中是 javascript,不过这对于学习正则表达式并没有太大的影响。

以下内容皆是《正则表达式必知必会》的读书笔记

1. 匹配单个字符

在此之前还是得先介绍下 javascript 中的正则表达式是怎样的、

在 javascript 中,正则表达式是书写在双斜杠之中的。

let reg = /he/; // 这个 /he/ 就是正则表达式。

1.1 匹配纯文本

let str = 'hello world'
let reg = /hello/;

console.log(str.match(reg)) // hello

字符串对象中的 match 函数会取出符合正则表达式规则的字符串。

上述代码中,正则表达式规则为hello,是个纯文本,所以很直白的就只能匹配到字符串中的 hello 。如果字符串中没有 hello ,那么将匹配不到,返回 null 。

1.2 全局匹配

全局匹配就是不会只匹配一次,会从头到尾检索字符串,而不是查到一个符合的结果就停止了。

通过下面两段代码,可以看出全局和非全局的区别

// 非全局
let str = 'hello world, hello'
let reg = /hello/;

console.log(str.match(reg)) //  hello
// 全局
let str = 'hello world, hello'
let reg = /hello/g;

console.log(str.match(reg)) //  hello hello

/hello/g 这后面的 g 便是表示全局的意思,如果不加上 g 的话,那么只能匹配到一个 hello ,后面的 hello 就匹配不到了。

1.3 忽略大小写

一般情况下 /hello/ 是无法匹配到 Hello 的,因为对大小写敏感,所以在必要时候可以在后面加上 i 来忽略大小写。

let str = 'Hello world, Hello'
let reg = /hello/i;

console.log(str.match(reg))  // Hello

如果要忽略大小写的同时进行全局匹配,那也很简单,加上 ig 即可。

let str = 'Hello world, Hello'
let reg = /hello/ig;

console.log(str.match(reg))  // Hello Hello

1.4 匹配任意字符

通过 . 这个特殊字符,可以匹配到任意的字符,包括 . 本身。

let str = 'hel, h5l, h55l, h.l'
let reg = /h.l/g;

console.log(str.match(reg)) // hel  h5l h.l

// 为什么没有匹配到 h55l ,因为 . 只能匹配一个任意字符

1.5 转义字符

通过上面章节的学习,我们知道 . 可以匹配任意字符,但如果说我只是想要匹配到一个 h.l 怎么做呢?

可以利用反斜杠 \ 进行转义,将 . 变成一个没有特殊意义的普通字符。

let str = 'hel, h5l, h55l, h.l'
let reg = /h\.l/g;

console.log(str.match(reg)) // h.l

凡是要匹配正则中特殊的字符,都需要用 \

2. 匹配一组字符

如果相匹配一组字符串中的 a 和 d,那么应该怎么做呢?

直接写 /a/g 虽然可以匹配到 a ,但是匹配不到 d 啊。

/ad/g 这匹配的又是 ad 这个整体。

/a./g ,这也不适合,会匹配到以 a 开头的任意字符串。

所以接下来便认识下 [] ,给字符设置一个区间。

2.1 [] 的使用

[] 是限定了一个范围,在这个范围内匹配

let str = 'ac,bc,cc,dc,fc'
let reg = /[ad]c/g;

console.log(str.match(reg)) // ac  dc

[ad] 就表示匹配 a 或 d ,其他则不匹配,因此整个正则表达式 /[ad]c/g 就是匹配 acdc

2.2 字符区间 [ - ]

这次的字符串是 ac,bc,cc,dc,fc, 5c, 8c ,我们要匹配到的内容是,第一个字符是任意一个小写字符,第二个字符是c。

通过上面章节的学习,我们想到的写法是 /[qwertyuiopasdfghjklzxcvbnm]c/g ,在 [] 中的字符是没有所谓的顺序的,我就是把键盘上的字母依次敲了一遍。

这样写虽然可以达到我们的要求,但是太过麻烦了。

因此出现了 - 这个字符,它代表的意思。

let str = 'ac,bc,cc,dc,fc, 5c, 8c'
let reg = /[a-z]c/g; // a到z

console.log(str.match(reg)); // ac,bc,cc,dc,fc,

a-z 就是 a 到 z ,同样还有其他的,比如 A-Z ,以及 0-9

注意:在 [] 中想要匹配 - 并不需要进行转义,直接 [-] 也是可以的。

let str = '123456-8';
let reg = /[6-8-]/g;

console.log(str.match(reg)) // '6', '-', '8'

2.3 取非匹配 [^]

取非匹配就是取反的意思。

[a-z] 是匹配 a 到 z 的字符串,呢么 [^a-z] 就是只要不是 a 到 z 的字符串都进行匹配。

let str = 'abc,cccc,c12c, e9cc'
let reg = /[a-z][^1-9][a-z]c/g;

console.log(str.match(reg)); // c,cc

首先,第一位是 a 到 z 之间, c 符合。

第二位是非数值,这个 , 英文逗号,也符合

第三位和第四位也符合。

2.4 使用 ^ 的注意事项

^ 必须写在 [] 里面,并且是第一位,才有取非的效果。

/[a-z^1-9]c/g这样是没有区分效果的,就只是个单纯的 ^ 字符

let str = 'abc,cccc,c12c, e9cc, 91cc, Ac, ^c-c'
let reg = /[a-z^1-9]c/g;

console.log(str.match(reg)); 
// ['bc', 'cc', 'cc', '2c', '9c', '1c', '^c']

2.5 | 的使用

如果现在有一个需求,要匹配到代码中的 varfunction 。如果使用 [] 似乎有些不太合适,因为 [varfunction] 只是匹配 varfunction 中的任意一个而已,这个时候就要使用 | ,它有的意思。

let str = `
    var a = 5;
    var            b = 6;
    function add(a, b){
        return a + b;
    }
`
let reg = /var|function/g;

console.log(str.match(reg)); // var var function

如果要有或这个功能,那么|就不能写在 [] 里面。

3. 元字符

元字符就是一些比较特殊的字符,比如可以匹配换行符,空白符等

元字符 描述
. 查找单个字符,除了换行和行结束符。
\w 查找数字、字母及下划线。
\W 查找非单词字符。
\d 查找数字。
\D 查找非数字字符。
\s 查找空白字符。
\S 查找非空白字符。
\b 匹配单词边界。
\B 匹配非单词边界。
\0 查找 NULL 字符。
\n 查找换行符。
\f 查找换页符。
\r 查找回车符。
\t 查找制表符。
\v 查找垂直制表符。
\xxx 查找以八进制数 xxx 规定的字符。
\xdd 查找以十六进制数 dd 规定的字符。
\uxxxx 查找以十六进制数 xxxx 规定的 Unicode 字符。

表格中的 \d 等价于 [0-9]\D 等价于 [^0-9]

// 匹配空白字符
let str = `
    if(false){
        console.log('111')
    }
`
let reg = /\t./g;

console.log(str.match(reg)); // ['\ti', '\t\t', '\t}']

4. 重复匹配

在之前都是只匹配一个字符,那么接下来就试试匹配多个字符吧。

4.1 匹配一个或多个字符

+ 加号就表示前面的表达式一个或多个字符,并且必须放在[]外面。

let str = `
    var a = 5;
    var            b = 6;
    function add(a, b){
        return a + b;
    }
`
let reg = /var[\s]+[a-zA-Z0-9_$]+[\s]+=[\s]+[a-z-A-Z0-9_$]/g;

console.log(str.match(reg)); 
// ['var a = 5', 'var    \t\tb = 6']

[\s]+ 这个就是匹配多个空白字符的意思。

4.2 匹配零个或多个字符 *

let str = `
    var a = 5;
    var            b = 6;
    var c=8;
    function add(a, b){
        return a + b;
    }
`
let reg = /var[\s]+[a-z-A-Z0-9_$]+[\s]*=[\s]*[a-z-A-Z0-9_$]/g;

console.log(str.match(reg)); 
//  ['var a = 5', 'var    \t\tb = 6', 'var c=8']

4.3 匹配零个或一个字符 ?

let str = `
    var a = 5;
    var            b =    6;
    var c=8;
    function add(a, b){
        return a + b;
    }
`
let reg = /var[\s]+[a-z-A-Z0-9_$]+[\s]?=[\s]?[a-z-A-Z0-9_$]/g;

console.log(str.match(reg)); // ['var a = 5', 'var c=8']

4.4 设定重复匹配的次数

{n}, n 为大于 0 的数值

let str = `abbc, abbbc, abbbd, abbbbd`
let reg = /a[b]{3}[a-z]/g;

console.log(str.match(reg)); // ['abbbc', 'abbbd', 'abbbb']

{n, m} n,m 均为大于 0 的数值,n 到 m 的次数

let str = `abbc, abbbc, abbbd, abbbbd`
let reg = /a[b]{1,3}[a-z]/g;

console.log(str.match(reg)); //['abbc', 'abbbc', 'abbbd', 'abbbb']

{n, } 至少重复 n 次

{,n} 最多重复 n 次

4.5 贪婪型和懒惰型

贪婪型会在文段中,从头匹配到尾部,而不是适可而止

let str = `
This offer is not available to customers living in 
<B>AK</B> and <B>HI</B>
`
let reg = /<[Bb]>.*<\/[Bb]>/g;

console.log(str.match(reg)); // <B>AK</B> and <B>HI</B>

<B>AK</B> and <B>HI</B> 这与预期不符合,不是两对 b 标签,因为第一个<B>和最后一个</B>之间的元素都被.*匹配了。

这时需要懒惰型的运算符 *?

let str = `
This offer is not available to customers living in 
<B>AK</B> and <B>HI</B>
`
let reg = /<[Bb]>.*?<\/[Bb]>/g;

console.log(str.match(reg)); // ['<B>AK</B>', '<B>HI</B>']

贪婪型运算符 *, +, {n,}
懒惰型运算符 *?, +?, {n,}?

5. 位置匹配

5.1 \b 的使用

\b 也叫边界,单词的边界。

let str = `
    var a = 5;
    var            b = 6;
    function add(a, b){
        return a + b;
    }
`
let reg = /\bvar\b/g;

console.log(str.match(reg)); // var var

通常在 \b 是以空白符为边界的,在英文句子中匹配词语就会使用到。

5.2 匹配开头和结尾

^ 开头,$ 结尾,这两个是写在[]外面才有这个效果的,没错,^ 写在 [] 里面就是取反,写在外面就是匹配开头。

^$ 是字符串的边界。

let str = `a13456789,  b123456789`
let reg = /^a[\w]+/g;

console.log(str.match(reg)); // ['a13456789']
let str = `a13456789b,  b123456789c`
// let reg = /[\w]+b$/g; // null 因为该字符串的尾部边界是c

let reg = /[\w]+c$/g; 

console.log(str.match(reg)); // b123456789c

5.3 分行匹配

在开头写上(?m),作用就是让 ^ 和 $ 能够匹配空白符,将空白符视为一个字符串分隔符。但是在 javascript 中分行模式叫做多行模式,而且也不是在开头加上 (?m) ,而是在末尾加上 m

let str = `
   // 数值
   let a = 1; 
  // 字符串
   let b = '11' 
   // 注释
`;
let reg = /^\s*\/.*/mg;
console.log(str.match(reg))

6. 子表达式

() 里面的就是正则表达式的子表达式。

现在我们要匹配一个 IP 地址:12.159.46.200

在不使用子表达式的情况下

let str = `12.159.46.200`
let reg = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{3}/g; 

console.log(str.match(reg)); // ['12.159.46.200']

在一整个正则表达式中 \d{1,3}.被重复写了三次,因此可以将其作为子表达式,在重复匹配三次,这样整个正则表达式就更加的简洁。

let str = `12.159.46.200`
let reg = /(\d{1,3}\.){3}\d{3}/g; 

console.log(str.match(reg)); // ['12.159.46.200']

子表达式允许嵌套,解读是由内而外,但可读性极差,这里不进行举例

7. 回溯引用

回溯引用是引用正则中的子表达式,相当于变量

7.1 使用回溯引用匹配

let str = `
    <h2>标题1</h2>
    this is title 1
    <h3>标题2</h3>
    this is title 2
    <h4>标题3</h4>
    this is title 3
`
let reg = /<[Hh]([1-6])>.*?<\/[Hh]\1>/g;

console.log(str.match(reg));
 // ['<h2>标题1</h2>', '<h3>标题2</h3>', '<h4>标题3</h4>']

\1表示第一个字表达式,\2就是第二个表达式,如此类推,没有个数限制,另外\0就是指整个表达式

7.2 使用回溯引用进行替换操作

通过下面代码对 var 关键字进行高亮标注

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>js函数</title>
    <link rel="stylesheet" type="text/css" href="./app.css">
</head>
    <pre id="pre">
        var a = 5
    </pre>
    <button id="btn">格式化</button>
    <script type="text/javascript" src="./app.js"></script>
</body>
</html>
.red{
    color: red;
}
let pre = document.getElementById('pre');
let btn = document.getElementById('btn');


let str = pre.innerText;
let reg = /(\bvar\b)/g;

function format(str, reg, html){
    return str.replace(reg, html);
}

btn.onclick = function(){
    let resule = format(str, reg, `<code class="red">$1</code>`);
    pre.innerHTML = resule
}

$1 代表第一个子表达式

9. 前后查找

这里的查找只是找到,但是不进行匹配,也就是不会输出作为结果,只是作为位置标记

9.1 向前查找 ?=

let str = `
<title>标题</title>
`

let reg = /.*(?=<\/[Tt][Ii][Tt][Ll][Ee]>)/g;

console.log(str.match(reg)) // ['<title>标题', '']

9.2 先后查找 ?<=

let str = `
<title>标题</title>
`

let reg = /(?<=\<[Tt][Ii][Tt][Ll][Ee]>).*/g;

console.log(str.match(reg)) // ['标题</title>']a

9.3 向前查找与向后查找结合

let str = `
<title>标题</title>
`

let reg = /(?<=\<[Tt][Ii][Tt][Ll][Ee]>).*(?=<\/[Tt][Ii][Tt][Ll][Ee]>)/g;

console.log(str.match(reg)) // ['标题']

9.4 前后查找取非

?! 负向前查找
?<! 负向后查找

这两个使用方法与上面一样,只不过是进行了取非,就不进行代码举例了,这个是很少用到的,感兴趣的小伙伴,自己动手研究下吧。

10. 使用变量

在 js 中如果想要在正则表达式之间加入变量,那么就必须要使用正则表达式的构造函数。

var v = "b";

var re =new RegExp("^\\d+" + v + "$","gim"); // re为/^\d+b$/gim

11. 后记

最后在《正则表达式必知必会》中其实还有一章是嵌入条件。它是一种威力强大的功能,但并非所有正则表达式中支持,而且不是经常用到,这里就不多说了。

感兴趣的自己研究一波吧。

posted @ 2022-04-06 15:33  司徒炼  阅读(451)  评论(0编辑  收藏  举报