从零开始学习正则表达式
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表通常被用来检索、替换那些符合某个模式(规则)的文本。
参考文章《正则表达式30分钟入门教程》
基本语法
1.元字符
字符 | 描述 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符,包括空格,制表符(Tab),换行符 |
\d | 匹配数字,(0,1,2,3....) |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
下面看几个例子👇
- 匹配已字母a开头的单词,先匹配某个单词的开始处(\b),然后是字母a,然后是任意的字母或数字或下划线或汉字,最后是单词结束处。
\ba\w*\b
- 匹配5位到12位数字
\d{5,12} //只能保证字符串中包含5到12位连续的数字,不能保证整个字符串就是5到12位数字。
^\d{5,12}$ //整个字符串必须是5到12个数字。
2.字符转义
如果你想查找元字符本身的话,比如查找.
或者*
,就出现了问题,你没办法指定他们,因为他们会被解释成别的意思,这时可以使用\
来取消这些字符的特殊含义。因此应该使用\.
或\*
,如果要查找\
本身,也需要用\\
。
regtext\.py 匹配 regtext.py
c:\\windows 匹配 c:\windows
3.重复
字符 | 描述 |
---|---|
* | 重复零次或多次 |
+ | 重复一次或多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
Windows\d+ //Windows后面跟一个或多个数字
\d{5,12} //匹配5到12个数字
4.字符类
要想查找字母,数字或者空白是很简单的,因为已经有了对应的元字符,如果你想匹配没有预定义元字符的字符集合,很简单只要在方括号里列出他们就行了。
[aeiou] //匹配任意一个英文元音字母
[.?!] //匹配标点符号.或者?或者!
[0-9] //匹配0到9之间的任意数字,含义同\d
下面看一个复杂的表达式:
\(?0\d{2}[) -]?\d{8}
首先分析上面表达式的含义,(
和)
也是元字符,所以这里需要转义。
首先是一个(
出现0次或者一次,然后是一个0,然后是两个数字,然后是)
或者空格或者-
中的零个或一个,最后是8个数字。能匹配到类似下面这种的字符串(011)12345678
,011-12345678
。
5.分支条件
不幸的是上面那个正则也能匹配到(011-12345678
等不正确的格式,要解决这个问题需要用到分支条件,如果满足其中任意一种规则都应该当成匹配,具体方法用|
隔开。
0\d{2}-\d{8}|0\d{3}-\d{7} //匹配3位区号,8位本地号,或者4位区号,7位本地号。
\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8} //匹配3位区号的电话号码,区号可以有小括号也可以没有,区号和本地号之间可以有空格,连接符,也可以没有
6.分组
我们已经提到了怎么重复单个字符 (直接在字符后面加上限定符就行了),但是如果想要重复多个字符又该怎么办,你可以用小括号来指定子表达式(也叫分组),然后你就可以指定这个子表达式的重复次数了,也可以对子表达式进行一些其他的操作。
(\d{1,3}\.){3}\d{1,3}
上面表达式的含义是:\d{1,3}
匹配1到3位数字,(\d{1,3}\.){3}
匹配1到3位数字加一个英文句号(这个整体也就是这个分组)重复3次,\d{1,3}
最后加一个1到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的分组中,然后是一个或多个空白字符\s+
,最后是分组1中捕获的内容(也就是前面匹配的那个单词)\1
。
\b(?<word>\w+)\b\s+\k<word>\b
可以自己指定表达式的组名,要指定一个子表达式的组名,可以使用(?<word>\w+)
或者(?'word'\w+)
。
常用的分组语法:
分类 | 语法 | 说明 |
---|---|---|
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
(?exp) | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) | |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 | |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 |
9.断言
👇通过几个例子进行学习:
- 匹配以
ing
结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you are dancing.
,他会匹配sing
和dance
。
\b\w+(?=ing\b)
- 匹配已
re
开头的后半部分(除了re以外的部分),如查找reading a book
,会匹配到ading
。
(?<=\bre)\w+\b
- 匹配这样的单词,它里面出现了字母q,但q后面跟的不是字母u,
(?!u)
它只匹配一个位置,并不消费任何字符。
\b\w*q(?!u)\w*\b
- 匹配三位数字,并且这三位数字的后面不能是数字
\d{3}(?!\d)
- 匹配前面不是小写字母的7位数字
(?<![a-z])\d{7}
10.贪婪匹配
当正则表达式中包含能接受重复的限定符时,通常的行为是匹配尽可能多的字符,例如a.*b
它会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab
,它会匹配整个字符串aabab
。这被称为贪婪匹配。
字符 | 描述 |
---|---|
*? | 重复零次或多次 ,但尽可能少重复 |
+? | 重复一次或多次,但尽可能少重复 |
?? | 重复零次或一次,但尽可能少重复 |
{n}? | 重复n次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
a.*?b
匹配最短的,以a开始,以b结束的字符串,如果匹配aabab
会匹配为aab
和ab
。
到这里,基本的语法已经讲解完了。
实用例子
1.匹配IP地址
匹配IP地址之前,需要首先知道IP地址的格式,IP地址分为4个字段 (IP地址每个字段都在0-255之间),例如:192.168.0.0
。所以我们的重点就是控制4个字段都在0-255之间。
每个字段至少有一位,通俗的写法是这样\d|\d\d|[01]\d\d|2[0-4]\d|25[0-5]
,将其简化后为\[01]?\d\d?|2[0-4]\d|25[0-5]
,注意第一个分支中使用\d\d?
而不使用\d?\d
,如果根本不存在数字,会更快的报告匹配失败,所以最后的正则表达式为:
^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$

2.验证用户名和密码
由数字和字母组成6-12,则不能为纯数字和纯字母组成的,(?![0-9]+$)
表示不能是纯数字,(?![a-zA-Z]$)
表示不能是纯字母。
^(?![0-9]+$)(?![a-zA-Z]$)[0-9a-zA-Z]{6,12}$

3.匹配对称的括号
匹配[呵呵]
,[微笑]
这类表情时会用到这种匹配,首先对[
进行转义为\[
,然后是一个分组([^ \[]*?)
,表示匹配除空格和[
以外的字符,重复零次或多次,但尽可能少重复,最后匹配]
。
\[([^ \[]*?)]

4.匹配微博@符号
只允许 英文数字下划线连字符,和 unicode 4E00~9FA5 范围内的中文字符
@[-_a-zA-Z0-9\u4E00-\u9FA5]+

5.匹配电话号码
匹配字符串中出现的所有的电话号码,正则表达式如下:
1[3578][0-9]{9}

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: @"1[3578][0-9]{9}" options:kNilOptions error:NULL];
NSArray<NSTextCheckingResult *> *phoneResult = [regex matchesInString:text.string options:kNilOptions range:text.yy_rangeOfAll];
for (NSTextCheckingResult *phone in phoneResult) {
if (phone.range.location == NSNotFound && phone.range.length <= 1) continue;
NSLog(@"%@",[text.string substringWithRange:phone.range])
}
正则表达式可视化网址Regexper