正则表达式
概念
简单来说,正则表达式是一种字符串匹配的模式。再换句话说,正则表达式就是记录文本规则的代码,用来查找符合某种复杂规则的字符串的需要。
你可能用过Windows下的通配符,例如*和?,与通配符类似,正则表达式也是进行文本匹配的工具,只不过比起通配符,它会更加精确的匹配。当然,这也取决于你编写的正则表达式的复杂性。
元字符
正则表达式是通过元字符匹配的,那元字符是什么呢?元字符简单来说,就是正则表达式的匹配规则,拿几个来举例子:
元字符 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\d | 匹配数字 |
[abcd] | 匹配abcd其中一个字符 |
[^abcd] | 匹配不是abcd的任意一个字符 |
[A-Z] | 匹配从A-Z中任意一个字符 |
[A-z] | 匹配从A-z任意一个字符(涵盖大小写) |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
以上只是几个常用的,真实用到的比上面的表格多得多。
其中^比较特殊,可以用来表示字符串的开始,如果放到[]中括号里,表示取反。
[]里面可以写单独一个字符,也可以写一个范围,举例子:
[a] 匹配A这个字符
[abcd] 匹配ABCD这几个字符中任意一个
[a-z] 匹配26个小写字符中的一个
[0-9] 这个跟\d一个意思,匹配0—9中任意一个数字
另外:这些元字符,区分大小写,大写表示取反,例如:\d匹配数字,\D匹配非数字。
字符转义
如果你想要匹配元字符本身的话,比如你想查找^,这时候你就需要用\来取消转义,两个\\匹配一个。
重复
我们前面知道\d匹配一个数字,那么如果我们想要匹配十个数字,应该怎么做?写十遍\d?
当然不会,正则表达式有几种限定符,用来匹配元字符出现的次数。
限定符 | 说明 |
---|---|
* | 重复0+次 |
+ | 重复1+次 |
? | 重复0或1次 |
{n} | 重复n次 |
{n,} | 重复n+次 |
{n,m} | 重复n—m次 |
那上面我们说的十个数字,就是:\d{10}
分支条件
我们知道外国人的名字格式为:FirstName·LastName,也就是名·姓的格式。
但也有些外国人有中间名,也就是:FirstName·MiddleName·LastName
那我们怎么同时匹配这两种格式的外国人的名称呢?
很简单:
\w+[·]\w+|\w+[·]\w+[·]\w+
我们知道在程序中|表示或的关系,在这里也是,只要符合两个正则表达式的任意一个,都算符合条件。
分组
重复十个数字,我们知道怎么重复,直接\d{10}就行了。
但是如果让你重复多个字符呢?例如:abc重复十遍。
这时候就要用到分组了,分组的元字符是小括号()。
假设说我们要匹配一个IP地址,IP地址的格式我们知道,是这样的:
XXX.XXX.XXX.XXX
其中每一段都是0—255,也就是1位到3位
那我们的正则表达式就可以这么写:
(\d{1,3}\.){3}\d{1,3}
但是这会有一个问题,它会匹配到错误地址,例如:
999.999.999.999
那我们重新写一下:
((\d|\d{2}|1\d{2}|2[0-4]\d|25[0-5]).){3}\d|\d{2}|1\d{2}|2[0-4]\d|25[0-5]
这可能就比较复杂了,这个匹配了所有情况,1位的地址、2位的地址、100—199的地址,200—249的地址,250—255的地址
后向引用
其实每一个分组都会默认指定一个编号,就像我们前面写的(\d{1,3}\.)的默认编号就是1,从左到右,依次累加。
如果我上面的关于IP的分组是这么写的
(\d{1,3}\.)(\d{1,3}\.)(\d{1,3}\.)
那么依次的分组顺序就是1,2,3
如果现在有一段字符串是这种格式XXX-XXX-XXX,第一段和第三段都是英文字母,并且相同,第二段是数字,使用分组怎么描述呢?
你肯定会知道是用分组,第一组和第二组肯定这么写([A-z]{3})-(\d{3})-
到第三组的时候,你发现出现问题了:怎么保证第三组和第一组一样呢?
这时候我们就要用到后向引用了,因为我们知道,第一组的编号是1,那我们直接可以通过\1来引用第一组的值,也就是这样写:
([A-z]{3})-(\d{3})-\1
看结果:
匹配成功!
当然,你觉得用1,2,3...这种编号表示,有的时候会搞混,你也可以给分组起名,给分组起名的语法有两种:
(?<Group1>[A-z]{3}),第一种方式是在分组中最前方加?<Name>的方式起名
(?'Group1'[A-z]{3}),第二种方式是把<>换成'',也就是在分组中最前方加?'Name'的方式起名
这种情况是用<GroupName>或者'GroupName'的方式引用
把我们之前的正则表达式换一下:
(?<Group1>[A-z]{3})-(\d{3})-\<Group1>
(?'Group1'[A-z]{3})-(\d{3})-'Group1'
试一下:
两种写法依旧匹配成功!
零宽断言
零宽断言用来指定一个位置,这个位置需要满足一些条件。
需要注意:零宽断言只匹配位置,而不会占用字符。
零宽先行断言:
全名:零宽度正预测先行断言
作用:匹配在某个表达式位置之前的符合条件的字符串
语法:(?=expression)
示例:
I am a competitor before.
匹配competitor这个单词,使用零宽先行断言可以这么写:
\b[A-z]+(?=\sbefore.)
我们匹配到了 before.之前的单词,也就是competitor。
零宽后发断言
全名:零宽度正预测后发断言
作用:匹配在某个表达式位置之后的符合条件的字符串
语法:(?<=expression)
示例:
I am a competitor before.
匹配competitor这个单词,使用零宽后发断言可以这么写:
(?<=\b[a]\s)\b[A-z]+\b
我们匹配到了a 后面的单词,也是competitor.
负向零宽断言
前面我们说过,\后面跟大小写是取正和取反的区别,那有零宽断言,也当然有负向零宽断言,一听就知道是用来取反的,跟零宽断言一样,负向零宽断言也包含先行断言和后发断言。
负向零宽先行断言:
全名:负向零宽度正预测先行断言
作用:匹配不在某个表达式位置前面的字符串
语法:(?!expression)
示例:
ABC-123456-123-ABC
使用负向零宽先行断言,匹配第三段的的值:
\b\d{3}(?!\d)
结果:
负向零宽先行断言:
全名:负向零宽度正预测后发断言
作用:匹配不在某个表达式后面的字符串
语法:(?<!expression)
示例:
ABC-123456-123-ABC
使用负向零宽后发断言,匹配第三段的值:
(?<!123)\d{3}\b
结果:
贪婪与懒惰
当你使用正则表达式来匹配字符的时候,它都会尽量多的匹配,这种方式称为贪婪匹配原则,也是默认的匹配模式。
示例:
abcabcab这个字符串,如果使用^.*b,它会直接匹配整个字符串。
而我们只希望它匹配第一个ab,也就是常说的懒惰匹配原则,这时候需要在后面加一个?
也就是^.*?b,匹配的结果就是ab。在普通限定符后面加上?,就会变成懒惰限定符。
鉴定符 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
其他
我们今天学习的只是正则表达式语法,但是在实际开发中,会有很多血想,例如:区分大小写、多行匹配、单行匹配、忽略空白等,这都取决于开发平台提供的API的支持,就写这么多吧,实在啃不动了...
之前学的都快忘了,再复习一遍...
大部分内容摘自及参考:http://deerchao.net/tutorials/regex/regex.htm,16年我就读的他写的关于正则表达式的文章,如今回来还是读他的,经典。
测试工具下载地址:http://deerchao.net/tools/regester/index.htm