一、
什么是正则表达式
在
Windows/Dos
下我们用通配符
(wildcard) *
和
?
来查找文件,和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求。
正则表达式也是用来进行文本匹配的工具,所以本文里多次提到了在字符串里搜索
/
查找,意思是在给定的字符串中,寻找与给定的正则表达式相匹配的部分。有可能字符串里有不止一个部分满足给定的正则表达式,这时每一个这样的部分被称为一个匹配。匹配在本文里可能会有三种意思:一种是形容词性的,比如说一个字符串匹配一个表达式;一种是动词性的,比如说在字符串里匹配正则表达式;还有一种是名词性的,就是上面说到的“
字符串中满足给定的正则表达式的一部分
”。
二、
入门
/b
是正则表达式规定的一个特殊代码,也叫元字符,代表着单词的开头或者结尾,也就是单词的分界处
,
它只匹配一个位置。
如果你要找的是
hi
后面不远处跟着一个
Lucy,
应该用
/bhi/b.*/Lucy/b
。
*
也是元字符,匹配除了换行符以外的任意字符,不过它代表的不是字符,也不是位置,而是数量。它指定
*
前面的内容以可以联系重复出现任意次以使整个表达式得到匹配。因此,
.*
连在一起就意味着任意数量的不包含换行的字符。现在
/bhi/b.*/Lucy/b
的意思就很明显了:先是一个单词
hi,
然后是任意个字符(但不能是换行),最后是
Lucy
这个单词。
0/d/d-/d/d/d/d/d/d/d/d
匹配这样的字符串:以
0
开头,然后就是两个数字,然后是一个连字号“
-
”,最后是
8
个数字。
/d
是一个新的元字符,匹配任意的数字(
0
—
9
)。
-
不是元字符,只匹配它本身——连字号。也可以简写成:
0/d{2}-/d{8}
。这里
/d
后面的
{2}
({8})
意思是前面
/d
必须连续重复匹配
2
次(
8
次)。
1
、
元字符
上节已经介绍了几个元字符
如
/b
,
.
,
*
,还有
/d
,当然还有更多的元字符可用,如
/s
匹配任意的空白符,包括空格,制表符(
Tab
),换行符,中文全角空格等。
/w
匹配字母或数字或下划线或汉字等。
下面举几个例子:
/ba/w*/b
匹配以字母
a
开头的单词——先是某个单词开始处(
/b
)
,
然后是字母
a
,
然后是任意数量的字母或数字(
/w*
)
,
最后是单词结束处(
/b
)
/d+
匹配
1
个或更多连续的数字。这里
+
是和
*
类似的元字符,不同的是
*
匹配重复任意次(可能是
0
),而
+
则匹配重复
1
次或更多次。
/b/w{6}/b
匹配刚好
6
个字母
/
数字的单词。
表
1.
常用的元字符
| |
代码
|
说明
|
.
|
匹配除换行符以外的任意字符
|
/w
|
匹配字母或数字或下划线或汉字
|
/s
|
匹配任意的空白符
|
/d
|
匹配数字
|
/b
|
匹配单词的开始或结束
|
^
|
匹配字符串的开始
|
$
|
匹配字符串的结束
|
元字符
^
以及
$
和
/b
有点类似,都匹配一个位置。
^
匹配你要用来查找的字符串的开头,
$
匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站要求你填写的
QQ
号必须为
5
位到
12
位数字时,可以使用:
^/d{5,12}$
。
2
、
字符转义
如果你想查找元字符本身的话,就会出问题。这时就必须使用
/
来取消这些字符的特殊意义。因此,应该使用
/
和
/*
,如果要查找
/
本身,就用
//
。
3
、
重复
你已经看过了前面的
*
,
+
,
{2}
,
{5,12}
这几个匹配重复的方式了。下面是正则表达式中所有的限定符。
表
2.
常用的限定符
| |
代码
/
语法
|
说明
|
*
|
重复零次或更多次
|
+
|
重复一次或更多次
|
?
|
重复零次或一次
|
{n}
|
重复n次
|
{n,}
|
重复n次或更多次
|
{n,m}
|
重复n到m次
|
下面是一些使用重复的例子:
Windows/d+
匹配
Windows
后面跟
1
个或更多数字
13/d{9}
匹配
13
后面跟
9
个数字(中国手机号)
^/w+
匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个得看选项设置)
4
、
字符类
如何
匹配没有预定义元字符的字符集合
(
比如元音字母
a,e,i,o,u)?
很简单,只需要在中括号中列出它们就行了,像
[aeiou]
就匹配任何一个英文元音字母,
[.?!]
匹配标点符号。
也可以指定一个字符范围,像
[0-9]
代表的含义与
/d
就是完全一致的:一位小数,同理
[a-z0-9A-Z]
也完全等用于
/w(
如果只考虑英文的话
)
下面是一个更复杂的表达式:
/(?0/d{2}[
)
-]?/d{8}
。
这个表达式可以匹配几个格式的电话号码,
像
(010)88886666
,或
022-22334455
,或
02912345678
等
,但它也能匹配
010)12345678
或
(022-87654321
这样的“不正确”的格式
。下文讲给出解决这个问题的答案。
5
、
反义
有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时就需要反义:
表
3.
常用的反义代码
| |
代码
/
语法
|
说明
|
/W
|
匹配任意不是字母,数字,下划线,汉字的字符
|
/S
|
匹配任意不是空白符的字符
|
/D
|
匹配任意非数字的字符
|
/B
|
匹配不是单词开头或结束的位置
|
[^x]
|
匹配除了x以外的任意字符
|
[^aeiou]
|
匹配除了
aeiou
这几个字母以外的任意字符
|
例子:
/S+
匹配不包含空白符的字符串
<a[^>]+>
匹配用尖括号括起来的以
a
开头的字符串
6
、
替换
正则表达式里的替换指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用
|
把不同的规则分隔开。举例说明:
0/d{2}-/d{8}|0/d{3}-/d{7}
这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,
8
位本地号(如
025-12345678
),一种是
4
位区号,
7
位本地号(
0510-4567891
)。
/(/0d{2}/)[-]?/d{8}|0/d{2}[-]?/d{8}
这个表达式匹配的结果例如
(025)-12345678
或
025-12345678
或
02512345678
/d{5}-/d{4}|/d{5}
这个表达式用于匹配美国的邮政编码。美国编码规则是
5
位数字,或者用连字号间隔的
9
位数字。这个例子还说明一个问题:使用替换时,顺序是很重要的。如果改成
/d{5}|/d{5}-/d{4}
的话,那么就只会匹配
5
位的邮政编码以及
9
位邮编的前
5
位。因为匹配替换时,将会从左到右地测试每个分支条件,如果满足了某个分支的话,就不会去管其它的替换条件了。
7
、
分组
上面讲到如果想重复多个字符,可以用小括号来指定子表达式(也叫做分组),然后就可以指定这个子表达式的重复次数了。
(/d{1,3}/.){3}/d{1,3}
是一个简单的
IP
地址匹配表达式。分析:
/d{1,3}
匹配
1
到
3
位的数字,
(/d{1,3}/.){3}
匹配三位数字加上一个英文句号
(
这个整体也就是这个分组
)
重复
3
次,最后再加上一个
1
到
3
位的数字(
/d{1,3}
)。
但它也将匹配
256.300.888.999
这种不可能存在的
IP
地址(
IP
地址中每个数字都不能大于
255
)。因为正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的
IP
地址:
((2[0-4]/d|25[0-5]|[01]?/d/d?)/.){3}(2[0-4]/d|25[0-5]|[01]?/d/d?)
。
对
2[0-4]/d|25[0-5]|[01]?/d/d?
进行分析:有三种匹配替换
2[0-4]/d
第
1
位
2
,
[0-4]
是
0
到
4
中的任意一位数字,
/d
是
0-9
任意数字;
25[0-5]
前两位是
25
,
[0-5]
是
0
到
5
中任意数;
[01]?/d/d?
是
01
重复
1
次或者
0
次,
/d
0-9
任意数字,
/d?
任意数字重复
1
次或者
0
次。
8
、
后向引用
使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一次出现的分组的组号为
1
,第二次为
2
,以此类推。
后向引用
用于重复搜索前面某个分组匹配的文本。例如,
/1
代表分组
1
匹配的文本。举例说明:
/b(/w+)/b/s+/1/b
可以用来匹配重复出现的单词,像
go go,kitty kitty
。首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(
/b(/w+)/b
),然后是
1
个或几个空白符(
/s+
),最后是前面匹配的那个单词(
/1
)。
你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:
(?<Word>/w+)
或者
(?
'Word'/w+)
)
,
这样就把
/w+
的组名指定为
Word
了。要反向引用这个分组捕获的内容,你可以使用
/k<Word>
,
所以上一个例子也可以写成这样:
/b(?<Word>/w+)/b/s+/k<Word>/b
。
使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:
表
4.
分组语法
| |
捕获
| |
(exp)
|
匹配
exp,
并捕获文本到自动命名的组里
|
(?<name>exp)
|
匹配
exp,
并捕获文本到名称为
name
的组里,也可以写成
(?'name'exp)
|
(?:exp)
|
匹配
exp,
不捕获匹配的文本,也不给此分组分配组号
|
零宽断言
| |
(?=exp)
|
匹配
exp
前面的位置
|
(?<=exp)
|
匹配
exp
后面的位置
|
(?!exp)
|
匹配后面跟的不是
exp
的位置
|
(?<!exp)
|
匹配前面不是
exp
的位置
|
注释
| |
(?#comment)
|
这种类型的组不对正则表达式的处理产生任何影响,用于提供注释让人阅读
|
我们已经讨论了前两种语法。第三个
(?:exp)
不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面。
9
、零宽断言
接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像
/b
,^,$
那样用于指定一个位置,这个位置应该满足一定的条件(断言),因此它们也被称为零度断言。举例说明:
(?=exp)
也叫零度宽正预测先行断言,它断言自身出现的位置的后面能匹配表达式
exp
。比如
/b/w+(?=ing/b)
,
匹配以
ing
结尾的单词的前面部分(除了
ing
以外的部分),如查找
I'm singing while you're dancing.
时,它会匹配
sing
和
danc
。
(?<=exp)
也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配的表达式
exp
。比如
(?<=/bre)/w+/b
会匹配以
re
开头的单词的后半部分(除
re
以外的部分),例如在查找
reading a book
时,它匹配
ading
。
例如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分
:
((?<=/d)/d{3})*/b,
用它对
1234567890
进行查找时结果是
234567890
下面这个例子同时使用了这两种断言:
(?<=/s)/d+(?=/s)
匹配以空白符间隔的数字(不包括这些空白符)。