正则表达式
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。 正则表达式 就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
概念
正则表达式,又称规则表达式,(Regular Expression,在代码中常简写为regex、regexp或RE),是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符"),是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。
字符 是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。 字符串 是0个或更多个字符的序列。 文本 也就是文字,字符串。说某个字符串 匹配 某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。
常用语法
- \b: 匹配一个单词边界,即字与空格间的位置。并不匹配单词分隔字符中的字符
在一篇英语文章中查找hi,如果直接使用hi查找,会查找出history、him、historical、hight等等,所以如果我们需要精确查找,可以使用 “\bhi\b”
- “.”:匹配除了换行符以外的任意字符,匹配的是字符
待匹配文本:history
正则表达式:.
匹配结果:
h
i
s
t
o
r
y
ps:换行符就是’\n’,ASCII编码为10(十六进制0x0A)的字符
- *:同样是元字符,不过它代表的不是字符,也不是位置,而是数量—它指定 *前边的内容可以连续重复出现任意次以使整个正则表达式得到匹配。通常与.一起用,". *"意味着任意数量的不包含换行的字符。
待匹配文本:history
正则表达式:.*
匹配结果:
history
- \d:匹配一位数字(0,或1,或2,或······)
待匹配文本:123456
正则表达式:\d
匹配结果:
1
2
3
4
5
6如果同时使用其他元字符,我们就能构造出功能更强大的正则表达式。比如下面例子:
待匹配文本:012-12345678 012-87654321
正则表达式:0\d\d-\d\d\d\d\d\d\d\d
匹配结果:
012-12345678
012-87654321
ps:–不是元字符,只匹配它本身——连字符或减号
- {n,m}或{n}:连续重复匹配(≥n 和 ≤m次)或(n次)
上述例子中存在许多烦人的重复操作(\d\d\d\d\d),可以简化为:
待匹配文本:012-12345678 012-87654321
正则表达式:0\d{2}-\d{8}
匹配结果:
012-12345678
012-87654321
- \s:匹配 任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等
- \w:匹配字母或数字
匹配要求:匹配以字母a开头的单词
待匹配文本:apple align amazing
正则表达式:\ba\w*\b
匹配结果:
apple
align
amazing
- +:和上面的"*"类似,不同的是 * 可以匹配重复任意次(可能是0次—— ≥0),而+则匹配重复1次或更多次(≥1)
待匹配文本:123456 456789
正则表达式:\d+
匹配结果:
123456
456789
- ^:匹配你要用来查找的字符串的开头,这和\b类型,也是匹配一个位置
$:匹配结尾,也是匹配一个位置
匹配要求:填写的内容必须是5位到12位数字
待匹配文本:123456789012
正则表达式:^\d{5,12}$
匹配结果:
123456789012
ps:因为使用了 ^ 和 $,所以输入的整个字符串都要用来和\d{5,12}来匹配,也就是说整个输入必须是5-12个数字
常用语法/元字符总结
代码 | 说明 |
---|---|
\b | 匹配单词的开始或结束 |
. | 匹配除换行符以外的任意字符 |
* | 重复零次或更多次 |
\d | 匹配数字 |
{n} | 重复n次 |
{n,} | 重复n到更多次 |
{n,m} | 重复n到m次 |
\s | 匹配任意的空白符 |
\w | 匹配字母或数字或下划线 |
+ | 重复一次或更多次 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
字符转义
如果你想查找元字符本身的话,比如你查找 “.” ,或者 *,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。
这时你就得使用 \ 来取消这些字符的特殊意义。因此,你应该使用 \. 和 \* 。当然,要查找 \ 本身,你也得用 \\ .
例如: unibetter.com 匹配 unibetter.com , C:\Windows 匹配 C:\Windows 。
字符类
要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),可以使用 [aeiou] 即匹配任何一个英文元音字母,同理**[.?!]**匹配任何一个标点符号(.?!)
我们也可以轻松地指定一个字符范围,像 [0-9] 代表的含义与**\d**就是完全一致的:一位数字,同理[0-9a-zA-Z]也完全等同于\w
下面我将讲解一个更复杂的表达式:
待匹配文本:(010)12345678 022-12345678
正则表达式:(?0\d{2}[- )}?\d{8}
匹配结果:
(010)12345678
022-12345678
分析:
首先是一个转义字符 \(
它能出现0或1次(?)
然后就是一个0
后面跟着2个数字(\d{2})
然后是)或-或空格其中一个({- )})
它出现0或1次(?)
最后跟着8个数字(\d{8})
分支条件
上述更复杂的表达式可以匹配010)12345678或(023-09183092这样的”不正确“的格式。要解决这个问题,我们需要用到分支条件。
正则表达式中的 分支条件 指的是有几种规则,如果满足其中一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。
我将给出以下示例:
待匹配文本:010-12345678 0376-2233445
正则表达式:0\d{2}-\d{8}|0\d{3}-\d{7}
匹配结果:
010-12345678
0376-2233445
ps:该表达式能匹配以连字号分隔的电话号码:一种是三位区号,8位本地号,另一种是四位区号,7位本地号
待匹配文本:01212345678 (012)12345678
正则表达式:(0\d{2})[- ]?\d{8}|0\d{2}[- ]?\d{8}
匹配结果:
01212345678
(012)12345678
ps:该表达式能匹配3位区号的电话号码:其中区号可以用小括号括起来也可以不用,区号与本地号可以用连字号或空格间隔,也可以没有间隔。
分组
我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定 子表达式 (也叫做 分组 ),然后你就可以指定这个子表达式的重复次数了
下面我将给出一段匹配IP地址的正则表达式
待匹配文本:192.168.100.1 192.168.156.1
正则表达式:(\d{1,3}.){3}\d{1,3}
匹配结果:
192.168.100.1
192.168.156.1
ps:
要理解这个表达式,请按下列顺序分析:
\d{1,3} 匹配1到3位的数字
(\d{1,3}.){3} 匹配三位数字加上一个英文句号 这个整体 重复三次
最后加上一个一到三位的数字(\d{1,3})
上述表达式只能匹配ip格式的字符串,但无法匹配是否违规的IP,如256.300.888.999这种不可能存在的IP地址
所以改进如下:
((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
反义
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母、数字、下划线 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结尾的位置 |
[^x] | 匹配除了x以外的字符 |
[^aeiou] | 匹配除了aeiou以外的字符 |
后向引用
使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个 组号 ,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
匹配要求:匹配重复的单词
待匹配文本:went went go go height height
正则表达式:\b(\w+)\b\s+\1\b
匹配结果:
went went
go go
height height
ps:其他的也不多说了,其中的\1是分组1中捕获的内容(也就是前面匹配的那个单词)
例如前面匹配了went,整个表达式就相当于\bwent\b\s+went\b
你也可以自己指定子表达式的组名。请使用这样的语法:
(?<Word>\w+)(或者把尖括号换成 ’ 也行:(?‘Word’\w+))
这样就把\w+的组名指定为Word了,要反向引用这个分组捕获的内容,可以使用 \k<Word>
分类 | 代码/语法 | 说明 |
---|---|---|
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
捕获 | (?<name>exp) | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name’exp) |
捕获 | (?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配编号 |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
零宽断言 | (?<=exp) | 匹配exp后面的位置 |
零宽断言 | (?!exp) | 匹配后面跟的不是exp的位置 |
零宽断言 | (?<exp) | 匹配前面不是exp的位置 |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
零宽断言
- (?=exp):也叫 零宽度正预测先行断言 ,它 断言自身出现的位置的后面能匹配表达式exp
待匹配文本:I am singing while you are dancing
正则表达式:\b\w+(?=ing\b)
匹配结果:
sing
danc
ps:匹配以ing结尾的单词的前面部分(除了ing以外的部分)
- (?<=exp):也叫 零宽度正回顾后发断言 ,它 断言自身出现的位置的前面能匹配表达式exp
待匹配文本:reading a book
正则表达式:(?<=\bre)\w+\b
匹配结果:
ading
ps:匹配 以re开头的单词的后半部分(除了re以外的部分)
- (?!exp):断言此位置的后面不能匹配表达式exp
待匹配文本:123b111a 1234
正则表达式:\d{3}(?!\d)
匹配结果:
123
111
234
ps:匹配 三位数字,而且这三位数字的后面不能是数字
- (?<!exp):断言此位置的前面不能匹配表达式exp
待匹配文本:
A2315461
B1234567
12345678202222
正则表达式:(?<![a-z])\d{7}
匹配结果:
123
111
234
ps:匹配前面不是小写字母的七位数字
- (?#comment):注释
使用示例:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)
ps:匹配前面不是小写字母的七位数字
贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符
贪婪匹配
待匹配文本:aabab
正则表达式:a.*b
匹配结果:
aabab
ps:匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。
懒惰匹配
有时,我们更需要 懒惰 匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ?
即.*?意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复
待匹配文本:aabab
正则表达式:a.*?b
匹配结果:
aab
ab
懒惰限定符
代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |