茫茫人海中,只找到了你
1. 什么是正则表达式
在某些情况下,我们总是要在某个文本中查找某些复杂规则的字符串,正则表达式就是用户描述这些规则的工具(正则表达式就是记录文本规则的代码)。
2. 举个简单的栗子
假设在一篇英文文章中,你要查找hi
这个单词,这时候查找到的不仅有hi
这个单词,还有类似于hidden
、hight
等等的,其实这不是我们想要的,我们要的仅仅就是hi
这个词,而这个时候,我们就应该使用\bhi\b
元字符
\b
:是正则表达式规定的一个特殊代码,代表着单词的开头或结尾,即单词的分界处。需要说明的是,\b
并不匹配单词分割字符(空格、标点符号、换行等),它只匹配一个位置。元字符
.
:用于匹配除了换行符以外的任意字符。元字符
*
:*
既不代表字符,也不是位置,而是数量;它指定前边的内容可以连续重复使用任意次以使得整个表达式得到匹配。
回到上面的例子,如果更换需求,现在需要查找的是hi###Max
,###
代表的是任意字符,这时候我们就可以使用上面的三个元字符组合来查找这个特定规则的字符串了,即: \bhi\b.*\bMax\b
。
目前我们只知道三个元字符,如果我们知道了更多的元字符,我们就可以构造出更强大的正则表达式。
1. 元字符
从以上的例子中我们已经知道了三个元字符了,下面我就来给大家再通过栗子介绍几个常用的元字符。
代码 | 说明 |
---|---|
\w |
匹配字母或者数字或者下划线或者汉字 |
\s |
匹配任意的空白符(包括空格、制表符、换行符、中文全角空格等) |
\d |
匹配数字 |
\b |
匹配单词的开始或者结束 |
^ |
匹配字符串的开始 |
$ |
匹配字符串的结束 |
\ba\w*\b
:匹配以字母a
开头的单词(显示以单词开始处\b
,然后是字母a
,然后是任意数量的字母或者数字\w
,最后是单词结尾处\b
)\d+
:匹配1个或者更多连续的数字。这里的+
是和*
类似的元字符,不同的是*
匹配重复任意次(可能是0次),而+
则匹配重复1次或者更多次。0\d{2}-\d{8}
:匹配以0开头,然后是两个数字,然后是一个连字号-
,最后是8个数字\b\w{6}\b
:匹配刚好6个字符的单词^\d{5,12}$
:匹配的是5位到12位数字(因为使用了^
和$
,所以输入的整个字符串都要用来和\d{5,12}
来匹配,即整个输入的必须是5到12个数字)
2. 字符转义
如果我想在文本中查找元字符本身的话,该怎么查找呢?这时候就要使用转义了
例如你要查找.
,就应该使用\.
。如果你要查找*
,就应该使用\*
。如果你要查找\
,就应该使用\\
3. 重复
代码 | 说明 |
---|---|
* |
重复零次或者更多次 |
+ |
重复一次或者更多次 |
? |
重复零次或者一次 |
{n} |
重复n次 |
{n,} |
重复n次或者更多次 |
{n,m} |
重复n到m次 |
4. 字符类
\d
:代表的是0-9的数字集合,怎么可以自定义字符集呢,可以使用[]
来实现
[0-9]
代表的意义与\d
就是完全一样的[a-z0-9A-Z]
也完全等同于\w
(不考虑汉字)\(?0\d{2}[) -]?\d{8}
:这个就稍微复杂了,这个表达式可以匹配下面几种格式的电话号码,如:(010)66668888
、022-11335577
、03312378956
分析一下:首先是一个转义字符\(
,后面跟了一个?
,代表这个(
可以出现一次或者零次,其次是0
,后面跟着2位数字\d{2}
,再后面是一个字符集分别是)
、、
-
,后面有个?
,代表这个字符集中的字符可以出现零次或者一次,再之后就是8位数字咯\d{8}
。
5. 分枝条件
正则表达式里的分枝条件
是指有几种规则,如果满足其中任意一种规则都应该匹配,具体方法是使用|
把不同的规则分隔开(分枝条件会从左到右测试执行每个条件)。
0\d{2}-\d{8}|0\d{3}-\d{7}
:这个表达式能够匹配到两种以连字符号分隔的电话号码:一种是三位区号、8位本地号码(如:101-88886666),一种是4位区号、7位本地号码(如:0371-6677889)\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}
:这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号之间可以用连字号或者空格间隔,也可以没有间隔。
6. 分组
分组的主要目的就是重复。上面我们已经知道了怎么重复单个字符(直接在字符后面加上限定字符就可以了);但如果想要重复多个字符改怎么办呢? 这就用到了分组:使用小括号来指定子表达式然后就可以指定这个子表达式的重复次数了。
-
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
:这个表达式匹配的是IP地址。下面就拆分开来逐个分析咯
从左到右
2[0-4]\d
:此表达式主要是匹配200-249的数字25[0-5]
:此表达式主要是匹配250-255的数字[01]?\d\d?
: 此表达式主要是匹配0-199的数字(2[0-4]\d|25[0-5]|[01]?\d\d?)\.
: 此表达式为匹配到0-255的数字,并在后面跟一个.
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}
:此表达式为将上一步的表达式重复3次
至于后面的跟前面的一样,就不分析了
7. 反义
代码 | 说明 |
---|---|
\W |
匹配任意不是字母、数字、下划线、汉字的字符 |
\S |
匹配任意不是空白符的字符 |
\D |
匹配任意非数字的字符 |
\B |
匹配不是单词开头或者结束的位置 |
[^x] |
匹配除了x 以外的任意字符 |
[^aeiou] |
匹配除了aeiou 这几个字母以外的任意字符 |
8. 向后引用
使用小括号指定一个子表达式后,匹配这个子表达式的文本可以在表达式或者其它程序中作进一步的处理。默认情况下,每个分组都会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
向后引用用于重复搜索前面某个分组匹配的文本。如:\1
,表示分组1匹配的文本
-
\b(\w+)\b\s+\1\b
:可以用来匹配重复的单词,如:go go或者kitty kitty。这个表达式首先是一个单词,也就是单词的开始处和结束处之间的多于一个的字母或者数字(\b(\w+)\b
),这个单词会被普货到编号为1的分组中,然后是1个或几个空白符(\s+
),最后是分组1中捕获的内容(\1)
当然也可以指定子表达式的组名。
要指定一个表达式的组名,需要使用这样的语法:(?
(或者把尖括号换成'
,即(?'Name'\w+)
),这样就把\w+
的组名指定为Name
了。要反向引用这个分组捕获的内容,可以使用\k
,所以上一个栗子也可以写成这样纸:\b(?
下表列出了小括号的一些常用的语法
分类 | 代码 | 说明 |
---|---|---|
捕获 | (exp) |
匹配exp ,并捕获文本到自动命名的组里 |
捕获 | (? |
匹配exp ,并捕获文本到名称为name的组里,也可以写成(?'name'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
。
(?<=\s)\d+(?=\s)
匹配以空白符间隔的数字(不包括这些空白符)。
10. 负向零宽断言
(?!exp)
也叫零宽度负预测先行断言,它断言此位置的后面不能匹配表达式exp。
(?也叫零宽度负回顾后发断言,它**断言此位置的前面不能匹配表达式
exp
。
-
\d{3}(?!\d)
:匹配三位数字,并且这三位数字后面不能是数字 -
\b((?!abc)\w)+\b
:匹配不包含连续字符串abc
的单词 -
(?:匹配前面不是小写字母的七位数字
-
(?<=<(\w+)>).*(?=<\/\1>)
:匹配不包含属性的简单HTML标签里的内容。分析:(?<=<(\w+)>)
指定了这样的前缀:被尖括号括起来的单词(比如可能是),然后是
.*
:任意的字符串,最后是一个后缀(?=<\/\1>)
,注意后缀中的\/
,它是前面介绍过的字符转义;\1
则是一个反向引用,引用的正式捕获的第一组,前面的(\w+)
匹配的内容,这样前缀实际上是的话,后缀就是
了
11. 注释
小括号的另一种用途是通过语法(?#comment)
来包含注释。如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)
。
12. 贪婪与懒惰
a.*b
:将会匹配最长的以a
开始,以b
结束的字符串。如果用此表达式搜索匹配aabab
,它会匹配整个字符串aabab
。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。以上个例子为准,我们只需要在其后面加上一个问号?
,这样,.*?
就意味着匹配任意数量的重复、但是在能使整个匹配成功的前提下使用最少的重复。
-
a.*?b
:匹配最短的,以a
开始,以b
结束的字符串。如果应用于aabab
的话,它会匹配aab
(第1-3个字符)和ab
(第4-5个字符)。
注意:为什么匹配到的第一个是aab
(1-3)而不是ab
(2-3)。简单说,正则表达式有一条规则比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权
下表为懒惰限定符
代码 | 说明 |
---|---|
*? |
重复任意次,但尽可能少重复 |
+? |
重复1次或者更多次,但尽可能少重复 |
?? |
重复0次或者1次,但尽可能少重复 |
{n,m}? |
重复n到m次,但尽可能少重复 |
{n,}? |
重复n次以上,但尽可能少重复 |
以上这些关于正则表达式的介绍已经足以让我们编写、改写、读懂一些常见的正则表达式,希望以上的介绍能够帮助到你。
3. 关于正则表达式的学习
正则表达式的学习最重要的就是栗子
,我们可以从不同的案例切入,理解案例并对其修改、实验(仅代表个人观点,有更好的观点可以和大家一起分享)。
关于测试:网上可以找到一些在线正则表达式测试的,童鞋们可以在那里测试验证自己写的一些表达式。
参考来源