1 第一章 正则表达式入门

正则表达式是一些用来匹配和处理文本的字符串

提示:同样的问题几乎总是有好几种不同的解决办法,这些办法并无优劣之分,你尽可以选择最熟悉的那种语法。

1
2
3
4
5
6
7
# 找出其中的 Ben
text: Hello, Ben, Ben

regex: Ben

# 匹配到两个 Ben,Java 中会以数组返回这两个 Ben
match: Ben Ben

2 第二章 匹配单个字符

1
2
3
4
5
6
7
# 找出其中包含 char 再加一个任意字符的字符串
text: char1 char2 char3 cccc

regex: char.

# . 匹配除换行符 \n 之外的任何单字符,匹配 . 使用 \.
match: char1 char2 char3

提示:如果你曾今使用过 DOS 的文件搜索功能,你讲发现正则表达式里 . 字符相当于 DOS 的 ? 字符。SQL 用户将注意到正则表达式里的 . 字符相当于 SQL 中的 _(下划线)字符。

1
2
3
4
5
6
7
# 找出其中的 file.xls
text: file.xls file.doc file.txt file1xls

regex: file\.xls

# 使用 \. 匹配 . 本身
match: file.xls

提示:如果需要搜索 \ 本身,就必须对 \ 字符进行转义,相应的转义序列是两个连续的反斜杠字符 \。

1
2
3
4
5
6
7
# 找出其中的 file\xls
text: file\xls filexx

regex: file\\xls

# 使用 \\ 匹配 \
match: file\xls

3 第三章 匹配一组字符

使用 [] 来定义一个字符集合

1
2
3
4
5
6
# 找出其中的 file
text: file fila filp

regex: fil[e]

match: file
1
2
3
4
5
6
7
# 找出其中的 regex 忽略大小写
text: RegEx or regex

regex: [Rr]eg[Ee]x


match: RegEx regex

提示:正则表达式中的字符区间(0~9,A~Z)可以使用 -(连字符)来定义

1
2
3
4
5
6
7
# 匹配 text 中的所有字符
text: na0.xls sa1.xls na2.xls na3.xls na4.xls na5.xls

# [0-5] 等同于 [012345]
regex: [ns]a[0-5]\.xls

match: na0.xls sa1.xls na2.xls na3.xls na4.xls na5.xls
  • A-Z,匹配从 A 到 Z 的所有大写字母
  • a-z,匹配从 a 到 z 的所有小写字母
  • A-z,匹配从 ASCII 字符 A 到 ASCII 字符 z 的所有字母。这个模式一般不常用,因为它还包含这 [ 和 ^ 等在 ASCII 字符表里排列在 Z 和 a 之间的字符。

提示:在定义一个字符区间的时候,一定要避免让这个区间的尾字符小于它的首字符(例如 [3-1])。这种区间是没有意义的,而且往往会让整个模式失效。

注意:-(连字符)是一个特殊的元字符,作为元字符它智能用在 [ 和 ] 之间,在字符集合意外的地方,- 只是一个普通字符,只能与 - 本身相匹配。因此,在正则表达式里,- 字符不需要被转义。

同一个字符集合里面可以有多个字符区间,例如:[A-Za-z0-9]

1
2
3
4
5
6
# 匹配出其中的颜色代码
text: <BODY BGCOLOR="#33aaff" TEXT="#00FF99" >

regex: #[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]

match: #33aaff #00FF99

提示:用元字符 ^ 来表明你想对一个字符集合进行取非匹配

1
2
3
4
5
6
# 匹配出其中的 filex.xls
text: file1.xls filex.xls

regex: file[^0-9]\.xls

match: filex.xls

注意:^ 的效果将作用于给定字符集合里的所有字符或字符区间,而不是仅限于紧跟在 ^ 字符后面的那一个字符或字符区间。

1
2
3
4
5
6
# 匹配出 file2
text: file0 file1 file2 file3 file4

regex: file[^0-13-4]

match: file2

4 第四章 使用元字符

4.1 匹配空白字符

提示:如果想匹配 [ 和 ] 需要对其进行转义 \[ 于 \]

元字符 说明
[\b] 回退(并删除)一个字符(Backspace 键)
\f 换页符
\n 换行符
\r 回车符
\t 制表符(Tab 键)
\v 垂直制表符

4.2 匹配数字

元字符 说明
\d 任何一个数字字符,等价于 [0-9]
\D 任何一个非数字字符,等价于 [^0-9]

4.3 匹配字母数字下划线

元字符 说明
\w 等价于 [a-zA-Z0-9_],也就是字母数字下划线
\W 等价于 [^a-zA-Z0-9_]
1
2
3
4
5
6
# 匹配其中的 asdf1234
text: asdf1234 sdfffsdf

regex: \w\w\w\w\d\d\d\d

match: asdf1234

4.4 匹配空白字符

元字符 说明
\s 任何一个空白字符,等价于 [\f\n\r\t\v]
\S 任何一个非空白字符,等价于 [^\f\n\r\t\v]

4.5 匹配 ASCII 中的字符

使用十六进制或是八进制就可以匹配到,比如说以十六进制匹配 \x0A 等价于 \n,\x61 等价于 a,以八进制进行匹配 \141 等价于 a

4.6 POSIX 字符集

提示:这种字符集并不是所有语言都支持

字符类 Java 中的形式 说明
[:alnum:] \p{Alnum} 等价于 [a-zA-Z0-9]
[:alpha:] \p{Alpha} 等价于 [a-zA-Z]
[:black:] \p{Black} 等价于 [\t ]
[:cntrl:] \p{Cntrl} ASCII 0-31 加上 ASCII 127
[:digit:] \p{Digit} 等价于 [0-9]
[:graph:] \p{Graph} 和 [:print:] 一样,但不包括空格
[:lower:] \p{Lower} 等价于 [a-z]
[:print:] \p{Print} 任何一个可打印字符
[:punct:] \p{Punct} 既不属于 [:alnum:] 也不属于 [:cntrl:] 的任何一个字符
[:space:] \p{Space} 等价于 [^\f\n\r\t\v ]
[:upper:] \p{Upper} 等价于 [A-Z]
[:xdigit:] \p{Xdigit} 任何一个十六进制数字,等价于 [a-fA-F0-9]

5 第五章 重复匹配

元字符 说明
+ 匹配一个或多个字符
* 匹配零个或多个字符
? 匹配零个或一个字符

提示:[0-9]+ 表示的是数字的一次或多次重复,[0-9+] 表示的是数字 0-9 和 + 字符,单独匹配 + 时可以使用 \ 转义,对于 + 、*、. 也可以使用 [] 来进行转义,看起来还挺清晰的。点那个再使用 [.] 时,[.] 和 [\.] 这两种形式的效果是一样的

提示:* 可以理解为,在我前面的字符集合是可选的,+ 可以理解为,在我前面的字符集合是必选的

提示:在匹配单个字符的时候也可以使用 [] 来使表达式更加清晰,比如 https?:// 和 http[s]?:// 明显可以看到后者更加清晰

5.1 指定重复次数

使用 {} 来指定重复次数 {8} 表示重复 8 次, {0,1} 表示最少重复 0 次,最多重复八次,相当于数学里面的前后都是闭区间,{3, } 表示重复 3 次或更多次(没上限)

提示:[-\/] 这个表达式表示的是匹配 - 或 / 这两个字符,\ 起到对 / 进行转义的作用

5.2 防止过度匹配

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 匹配出 <B>AK</B> 和 <B>HI</B>
text: <B>AK</B> and <B>HI</B>

regex: <[Bb]>.*<[Bb]>

# 可以看到多了一个 and,这便是过度匹配
match: <B>AK</B> and <B>HI</B>

# 这样写便可以起到匹配的作用
regex: <[Bb]>.*?<[Bb]>

match: <B>AK</B> <B>HI</B>
贪婪型元字符 懒惰型元字符
匹配尽可能多的字符 匹配尽可能少的字符
* *?
+ +?
{n, } {n, }?

6 第六章 位置匹配

6.1 单词边界

\b 匹配单词边界的位置,比如说 b ,可以理解为 空格b空格,空格和 b 之前的位置就是 \b 匹配的位置

注意:\b 到底匹配什么东西呢?正则表达式引擎不懂英语,也不知道什么是单词边界。简单地说,\b 匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是 \w 相匹配的字符)和一个不能用来构成单词的字符(也就是 \W 相匹配的字符)之间。b 是英文 boundary(边界)的首字母。

注意:\b 匹配只匹配一个位置,不匹配任何字符。比如说 \bcat\b 只匹配到 3 个字符而不是 5 个字符。

\B 将匹配不是单词边界的位置,比如说 - ,可以理解为 空格-空格,由于 - 不属于 \w 的范围,所以 空格 和 - 之间的位置匹配 \B

注意:正则表达式还支持 \< 匹配单词的开头,\> 匹配单词的结束,不过,虽然这两种元字符可以提供粒度更细的控制,但支持它们的正则表达式引擎并不多见。

6.2 字符串边界

^ 用来定义字符串的开头,$ 用来定义字符串的结尾

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 检测出下文是否是一个合法的 xml 文件
text:
test
<?xml version="1.0" ?>

# 如果使用这个正则表达式,那么就会匹配到第二行
# 但是第一行有东西,说明不是合法的 xml 文件
regex: <\?xml.*\?>

# 而下面这个正则则能检查出该文件不是 xml 文件
regex: ^\s*<\?xml.*\?>

6.2.1 分行匹配模式

在分行匹配模式下 ^ 不仅匹配字符串最开始的位置,还匹配换行符后面的开始位置,类似的, $ 不仅匹配正常的字符串结尾,还将匹配换行符后面的结束位置。使用时将 (?m) 放在整个模式的最前面即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
匹配出其中的所有注释
text:
public class Main {
    // main
    public static void main(String[] args) {
        // todo
    }
}

regex: (?m)^\s*//.*$

match: // main  // todo

警告:有许多正则表达式实现不支持 (?m)

注意:有些正则表达式实现还支持使用 \A 来定义一个字符串的开始,以 \Z 来定义一个字符串的结束的做法。此时,\A 和 \B 的作用将基本等价于 ^ 和 $,但请注意,\A 和 \B 不会因为加上了 (?m) 前缀而改变行为。换句话说,在跨行匹配模式下使用 \A 和 \B 的做法不会受到在分行匹配模式下使用 ^ 和 $ 的效果。

7 第七章 使用子表达式

看下面这个场景

1
2
3
4
5
6
7
8
# 匹配出其中的 windows 2000 windows 2000 windows 2000
text: windows 2000 windows 2000 windows 2000

# 这样写很啰嗦
regex: windows 2000 windows 2000 windows 2000

# 可以写成这样
regex: (windows 2000 ){2, }windows 2000

提示:( 和 ) 是元字符,匹配本身需要用 \( \)

1
2
3
4
5
# 匹配其中的年份
text: 1920-01-01

# 其中的 | 表示或
regex: (19|20)\d{2}

8 第八章 回溯引用:前后一致匹配

提示:回溯引用只能用来引用模式里的子表达式即用 () 括起来的正则表达式片段。

提示:回溯引用通常从 1 开始计数(\1、\2 等),在许多实现中,第 0 个匹配 \0 可以用来代表整个正则表达式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 匹配其中的 html 标签和其中的内容
text:
<h1> h1 </h1>
<h2> h1 </h2>
<h2> h1 </h3>

# 只匹配第一行第二行,不匹配第三行
regex: <h([1-3])>.*</h\1>

match:
<h1> h1 </h1>
<h2> h1 </h2>

8.1 使用正则表达式进行替换

使用替换操作需要给入两个表达式,一个是搜索的表达式,另一个是替换的表达式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 将 313-555-1234 替换成 (313) 555-1234
text:
313-555-1234
248-555-9999

正则表达式: (\d{3})(-)(\d{3})(-)(\d{4})

替换表达式: ($1) $3-$5

结果:
(313)555-1234
(248) 555-9999

有些正则表达式实现允许我们使用元字符对字母进行大小写转换

元字符 说明
\E 结束符号,于 \L 与 \U 组合使用
\l 把下一个字符转换为小写
\L 把 \L 到 \E 之间的字符全部转换为小写
\u 把下一个字符转换为大写
\U 把 \U 到 \E 之间的字符全部转换为大写
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
text:
<BODY>
    <H1> Welcome to my Homepage</H1>
    Content is divided into two sections <BR> 
    <H2>Coldfusion</H2>
    Information about Macromedia Coldfusion.
    <H2>Wireless</H2>
    Information about Bluetooth, 802.11, and more  
    <H2> This is not valid HTML</H3>
</BODY>

正则表达式: (<[Hh]1>)(.*?)(</[Hh]1>)

替换表达式:$1 \U$2\E$3
<BODY>
    <H1> Welcome to my Homepage</H1>
    Content is divided into two sections <BR> 
    <H2>COLDFUSION</H2>
    Information about Macromedia Coldfusion.
    <H2>WIRELESS</H2>
    Information about Bluetooth, 802.11, and more  
    <H2> This is not valid HTML</H3>
</BODY>

9 第九章 前后查找

一个向前查找模式其实就是一个以 ?= 开头的子表达式,需要匹配的文本跟在 = 的后面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 提取出其中的协议
text:
http://yuanzx.me/
https://yuanzx.me/

# 表示查找到 : 的位置为止,但不包含 :
正则表达式:.+(?=:)

match:
http
https

提示:任何一个子表达式都可以转换为一个向前查找表达式,只要给它加上一个 ?= 前缀即可。

?<= 便是向后查找符号

1
2
3
4
5
6
7
8
9
text:
apple: $22
orange: $32

regex: (?<=\$)[0-9]+

match:
22
32

警告:向前查找模式的长度是可变的,它们可以包含 . 和 + 之类的元字符,所以非常灵活。而向后查找模式只能是固定长度,这是一条几乎所有的正则表达式都遵守的限制。

1
2
3
4
5
6
7
# 查找出 h1 中间的内容
text: <h1> h1 </h1>

regex: (?<=<h1>).*(?=</h1>)

match:
 h1 

参考书籍

  1. 《正则表达式必知必会》