正则表达式

推荐

基础

1.元字符

元字符 说明
. 匹配除换行符以外的任意字符
\w 匹配字母、数字、下划线、汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始结束
[] 匹配方括号内的任意字符
[^] 匹配除了方括号里的任意字符集
(xyz) 匹配与xyz完全相等的字符串
^ 匹配字符串的开始
$ 匹配字符串的结束

根据元字符,可以写一些简单的正则:

1.匹配所有ab开头的字符串

1
\bab  或者  ^ab

2.匹配9位数字的QQ号:

1
^\d\d\d\d\d\d\d\d\d$

3.匹配1开头的7位数字的电话号码

1
^1\d\d\d\d\d\d$

4.匹配The car parked in the garage.中的Thethe

1
2
3
4
5
正则:
[Th]he

结果:
[The] car parked in [the] garage.

5.匹配The car parked in the garage.中不是car的其他结果:

1
2
3
4
5
正则:
[^c]ar

结果:
The car [par]ked in the [gar]age.

2.重复限定符

用元字符能写不少正则表达式,但是向上面的一串\d还不够简洁。
为了处理重复的问题,正则提供了重复限定符号:

语法 说明
* 重复0次多次
+ 重复1次多次
重复0次1次
{n} 重复n次
{n,} 重复n次更多次
{n,m} 重复n到m次

有了这些限定符之后,就能对元字符进行改造:

1.匹配9位数字的QQ号:

1
^\d{8}$

2.匹配1开头的7位数字的电话号码:

1
^1\d{6}$

3.匹配14~18位的银行卡号:

1
^\d{14,18}$

4.匹配以a开头的,0个或多个b结尾的字符串

1
^ab*$

3.分组

从上面例4中看到,*限定符是作用在与他左边最近的一个字符,那么如果我想要ab同时被*限定那怎么办呢?

正则表达式中用小括号()来做分组,也就是括号中的内容作为一个整体。

因此当我们要匹配多个ab时,我们可以这样

如:匹配字符串中包含0到多个ab开头:

1
^(ab)*

4.转义

正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,需要在要转义的字符前面加个斜杠,也就是\即可。如将分组所使用的括号转义为匹配对象:

1
^(\(ab\))*

5.条件或

国内手机号码它们都有属于自己的号段,比如联通有130/131/132/155/156/185/186/145/176等号段,假如匹配一个联通的号码就要用到一些并列的条件,也就是“或”。

正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。

那么我们就可以用或条件来处理这个问题:

1
^(130|131|132|155|156|185|186|145|176)\d{8}$

6.区间

正则提供一个元字符中括号 [] 来表示区间条件。

1.限定0到9 可以写成[0-9]

2.限定A-Z 写成[A-Z]

3.限定某些数字 [165]

那上面的正则我们还改成这样:

1
^((13[0-2])|(15[56])|(18[5-6])|145|176)\d{8}$

进阶

1.零宽断言

1.断言:俗话的断言就是“我断定什么什么”,而正则中的断言,就是说正则可以指明在指定的内容的前面或后面会出现满足指定规则的内容,意思正则也可以像人类那样断定什么什么,比如”ss1aa2bb3”,正则可以用断言找出aa2前面有bb3,也可以找出aa2后面有ss1.
2.零宽:就是没有宽度,在正则中,断言只是匹配位置,不占字符,也就是说,匹配结果里是不会返回断言本身。

符号 描述 解释
?=pattern 正先行断言-存在 匹配pattern前面的内容
?<=pattern 正后发断言-存在 匹配pattern后面的内容
?!pattern 负先行断言-排除 匹配pattern前面的内容
?<!pattern 负后发断言-排除 匹配pattern后面的内容

正先行断言-存在: “(T|t)he(?=\sfat)” => The fat cat sat on the mat.

正后发断言-存在: “(?<=(T|t)he\s)(fat|mat)” => The fat cat sat on the mat.

负先行断言-排除: “(T|t)he(?!\sfat)” => The fat cat sat on the mat.

负后发断言-排除: “(?<!(T|t)he\s)(cat)” => The cat sat on cat.

例子:假设我们要用爬虫抓取csdn里的文章阅读量。通过查看源代码可以看到文章阅读量这个内容是这样的结构

1
<span class="read-count">阅读数:641</span>

其中也就‘641’这个是变量,也就是说不同文章不同的值,当我们拿到这个字符串时,需要获得这里边的‘641’有很多种办法,但如果正则应该怎么匹配呢?

1).正向先行断言(正前瞻):

  • 语法:**(?=pattern)**
  • 作用:匹配pattern表达式前面的内容,不返回本身

要取到阅读量,在正则表达式中就意味着要能匹配到</span>前面的数字内容:

1
2
3
4
5
6
7
8
内容:
<span class="read-count">阅读数:641</span>

正则:
.+(?=</span>)

匹配结果:
<span class="read-count">阅读数:641

我们要的只是前面的数字呀,那也简单咯,匹配数字 \d,那可以改成:

1
2
3
4
5
6
7
8
内容:
<span class="read-count">阅读数:641</span>

正则:
\d+(?=</span>)

匹配结果:
641

2).正向后行断言(正后顾):

  • 语法:**(?<=pattern)**
  • 作用:匹配pattern表达式后面的内容,不返回本身

先行是匹配前面的内容,那后行就是匹配后面的内容。
上面的栗子,也可以用后行断言来处理:

1
2
3
4
5
6
7
8
内容:
<span class="read-count">阅读数:641</span>

正则:
(?<=<span class="read-count">阅读数:)\d+

匹配结果:
641

**3).负向先行断言(负前瞻)**:

  • 语法:**(?!pattern)**
  • 作用:匹配pattern表达式前面的内容,不返回本身,即匹配结果不包含pattern表达式

例如对regex represents regular expression,要想匹配除regex和regular之外的re:

1
2
3
4
5
6
7
8
内容:
regex represents regular expression

正则:
re(?!g)

匹配结果:
regex [re]p[re]sents regular exp[re]ssion

2.捕获和非捕获

单纯说到捕获,他的意思是匹配表达式,但捕获通常和分组联系在一起,也就是“捕获组”

捕获组:匹配子表达式的内容,把匹配结果保存到内存中中数字编号或显示命名的组里,以深度优先进行编号,之后可以通过序号或名称来使用这些匹配结果。

而根据命名方式的不同,又可以分为两种组:

1).数字编号捕获组:

  • 语法:**(exp)**
  • 解释:从表达式左侧开始,每出现一个左括号和它对应的右括号之间的内容为一个分组,在分组中,第0组为整个表达式,第一组开始为分组。

比如固定电话的:020-85653333
他的正则表达式为:**(0\d{2})-(\d{8})**
按照左括号的顺序,这个表达式有如下分组:

序号 编号 分组 内容
0 0 (0\d{2})-(\d{8}) 020-85653333
1 1 (0\d{2}) 020
2 2 (\d{8}) 85653333

2).命名编号捕获组:

  • 语法:**(?<name>exp)**
  • 解释:分组的命名由表达式中的name指定

比如区号也可以这样写:(?<quhao>\0\d{2})-(?<haoma>\d{8})

按照左括号的顺序,这个表达式有如下分组:

序号 编号 分组 内容
0 0 (0\d{2})-(\d{8}) 020-85653333
1 quhao (0\d{2}) 020
2 haoma (\d{8}) 85653333

3).非捕获组

  • 语法:**(?:exp)**
  • 解释:和捕获组刚好相反,它用来标识那些不需要捕获的分组,通俗讲,就是可以根据需要去保存你的分组。

比如上面的正则表达式,程序不需要用到第一个分组,那就可以这样写:

1
(?:\0\d{2})-(\d{8})
序号 编号 分组 内容
0 0 (0\d{2})-(\d{8}) 020-85653333
1 1 (\d{8}) 85653333

3.反向引用

捕获会返回一个捕获组,这个分组是保存在内存中,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用

根据捕获组的命名规则,反向引用可分为:

1).数字编号组反向引用:\k\number

2).命名编号组反向引用:\k\name

例:

比如要查找一串字母aabbbbgbddesddfiid里成对的字母

思路:

  • a. 匹配到一个字母
  • b. 匹配下一个字母,检查是否和上一个字母一样
  • c. 若一样,匹配成功,否则失败

这里的思路2中匹配下一个字母时,需要用到上一个字母,那怎么记住上一个字母呢?这下捕获就有用处啦,我们可以利用捕获把上一个匹配成功的内容用来作为本次匹配的条件.

首先匹配一个字母:\w

需要将匹配的第一个字母做成分组才能捕获,因此:(\w)

然后用这个捕获组作为条件,则:(\w)\1

在默认情况下都是以数字来命名,而且数字命名的顺序是从1开始的,\1表示第一个捕获组,也就是(\w)

1
2
3
4
5
6
7
8
9
10
11
12
13
内容:
aabbbbgbddesddfiid

正则:
\(w)\1

结果:
aa
bb
bb
dd
dd
ii

4.替换

有了引用那么我们可以根据捕获组进行直接对应内容的替换。

例如想要将

1
2
1.Anne
2.John

每行的编号替换成带引号的编号,即"1","2"这样替换就很方便了:

1
2
3
4
5
6
7
8
9
10
11
内容:
1.Anne
2.John

正则:
- 查找: ^\d
- 替换: "$1"

结果:
"1".Anne
"2".John

5.贪婪和非贪婪

1).贪婪

  • 贪婪匹配:当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符,这匹配方式叫做贪婪匹配。
  • 特性:一次性读入整个字符串进行匹配,每当不匹配就舍弃最右边一个字符,继续匹配,依次匹配和舍弃(这种匹配-舍弃的方式也叫做回溯),直到匹配成功或者把整个字符串舍弃完为止,因此它是一种最大化的数据返回,能多不会少。

前面的重复限定符就是贪婪量词,比如表达式:

1
\d{3,6}

用来匹配3到6位数字,在这种情况下,它是一种贪婪模式的匹配,也就是假如字符串里有6个个数字可以匹配,那它就是全部匹配到。

1
2
3
4
5
6
7
8
9
10
11
内容:
61762828 176 2991 871

正则:
\d{3,6}

结果:
617628
176
2991
871

由结果可见:本来字符串中的“61762828”这一段,其实只需要出现3个(617)就已经匹配成功了的,但是他并不满足,而是匹配到了最大能匹配的字符,也就是6个。

多个贪婪量词凑在一起,那他们是如何支配自己的匹配权的呢?

多个贪婪在一起时,如果字符串能满足他们各自最大程度的匹配时,就互不干扰,但如果不能满足时,会根据深度优先原则,也就是从左到右的每一个贪婪量词,优先最大数量的满足,剩余再分配下一个量词匹配。

1
2
3
4
5
6
7
8
9
10
内容:
61762828 176 2991 87321

正则:
(\d{1,2})(\d{3,4})

结果:
617628
2991
87321

“617628” 是前面的\d{1,2}匹配出了61,后面\d{3,4}匹配出了7628
“2991”是前面的\d{1,2}匹配出了29,后面\d{3,4}匹配出了91
“97321”是前面的\d{1,2}匹配出了97,后面\d{3,4}匹配出了321

2).懒惰(非贪婪)

  • 懒惰匹配:当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能少的字符,这匹配方式叫做懒惰匹配。
  • 特性:从左到右,从字符串的最左边开始匹配,每次试图不读入字符匹配,匹配成功,则完成匹配,否则读入一个字符再匹配,依此循环(读入字符、匹配)直到匹配成功或者把字符串的字符匹配完为止。
  • 懒惰量词是在贪婪量词后面加个?
代码 说明
*? 重复任意次数,但尽可能少重复
+? 重复1次或更多次,但是尽可能少重复
?? 重复0次或1次,但是尽可能少重复
{n,m}? 重复n到m次,但是尽可能少重复
{n,}? 重复n次以上,但是尽可能少重复
1
2
3
4
5
6
7
8
9
10
内容:
61762828 176 2991 87321

正则:
(\d{1,2}?)(\d{3,4})

结果:
61762
2991
87321

“61762” 是前面的懒惰匹配\d{1,2}?匹配出了6,后面贪婪匹配\d{3,4}匹配出了1762
“2991”是前面的懒惰匹配\d{1,2}?匹配出了2,后面贪婪匹配\d{3,4}匹配出了991
“87321”是前面的\d{1,2}匹配出了8,后面\d{3,4}匹配出了7321

6.反义

若不想匹配某些内容,就可以使用反义元字符,反义元字符很大一部分就是元字符大写:

元字符 解释
\W 匹配任意不是字母、数字、下划线、汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!