定义
正则表达式就是记录文本规则的代码。
正则表达式测试
元字符
常用元字符:
代码 | 说明 |
---|---|
. | 匹配除换行符外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
重复
代码/语法 | 说明 |
---|---|
* | 重复零次到无限次 |
+ | 重复一次到无限次 |
? | 重复零次或者一次 |
{n} | 重复n次 |
{n,} | 重复n次到无限次 |
{n,m} | 重复n到m次 |
例如:Windows\d+
匹配Windows后面接一次到无限次的数字
字符转义
如果查找元字符本身就,使用 \ 来进行转义,例如 dwk715.github 使用dwk715\.github
来匹配.
字符类
[]可以用于匹配字符集
例如:
当要匹配元音字母时,使用[aeiou]
进行匹配
匹配电话号码
1 | \(?0\d{2}[)-]?\d{8} |
匹配电话号码,如(010)88886666,或022-22334455,或02912345678等。
但这个表达式存在问题,会匹配到010)12345678或者(10112345678这样错误的号码
分枝条件
分枝条件就是为了解决这个而引入的
正则表达式里的分枝条件指有几种规则,如果满足其中的一条规则就完成匹配,具体方法用|
把不同的规则分割开。
例如:解决上面说到的问题,应该使用正则表达式如下:
1 | 0\d{2}[-]?\d{8}|\(+0\d{2}\)+[-]?\d{8} |
匹配不带()
的或者带()
的,但要注意匹配的顺序,如匹配🇺🇸的邮政编码,应该使用一下正则:
1 | \d{5}-\d{4}|\d{5} |
如果写成
1 | \d{5}|\d{5}-\d{4} |
则只会匹配5位邮政编码以及9位邮政的前5位,原因就是匹配分枝条件时,将会从左到右测试每个条件,如果满足某个分枝的话,就不会再去尝试其他的条件了。
分组
分组(又称子表达式)用于匹配多个重复字符。
举个例子,匹配合规的IP地址(这个很复杂)应该有如下考虑:
- 由四个不大于255的数字组成
- 数字应该为24(0~9)或者25(0~5)或者01后跟一个数字或者
- 可以存在前导0,如01.02.03.04这样的IP地址
具体实现如下:
1 | (2[0-4]\d) |
后向引用
后向引用用于重复搜索前面某个分组匹配的文本。使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其他程序中作进一步的处理。
例如\1
代表分组1匹配的文本。
例子:
1 | \b(?<Word>\w+)\b\s+\k<Word>\b |
可以匹配重复的单词,如go go,或者fuck fuck。也可以省略k,写成
1 | \b(?<Word>\w+)\b\s+\<Word>\b |
常用的分组语法
分类 | 代码/语法 | 说明 |
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
?(<name>)exp | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp) | |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 | |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 | |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
零宽断言
零宽断言用于匹配某些内容(但不包括这些内容)之前或者之后的东西。
举个例子:
要匹配一个句子中以ing结尾前的部分
1 | \b\w+(?=ing\b) |
这被称为零宽度正预测先行断言,它断言自身出现的位置后面能匹配表达式exp的。
再举个例子:
1 | (?<=\bre)\w+\b |
这个例子会断言自身出现的位置前面能匹配表达式exp的。就说在reading a book中会匹配到ading(JS不支持,垃圾!)
负向零宽断言
有时候会遇到这样的问题,例如,要匹配一个带q的但q后面不能跟着u的单词,就可以用反义来写:
1 | \b\w*q[^u]\w*\b |
但这个正则会除问题,例如在匹配Iraq fighting时,就会匹配到整个字符串,这是因为[^u]
总会要匹配一个字符,所以如果q是单词结束的最后一个字符的话,[^u]
就会匹配到空格,要解决这个问题,就用到了零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。例如:
1 | \b((?!abc)\w)+\b |
匹配不包含连续字符串abc的单词
同理,使用零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:
1 | (?<![a-z])\d{3} |
前面不是小写字母的3位数字
一个复杂的不正确的例子:匹配不包含属性的简单html标签内的内容,
这个例子中需要有一个<…>和一个</…>并且…的内容相同,那么就可以写成下面这种形式
1 | (?<=<(\w+)>).*(?=<\/\1>) |
在这个正则中,首先匹配一个带<…>的部分,此部分中间有一个w+的字符串,然后跟着任意的字符串.*
,接下来匹配一个</…>的部分,...
需要和前面的…相同,这样就匹配了一个不包含属性的简单html标签。(js不支持lookbehind)。
贪婪与懒惰
贪婪就是匹配尽可能多的字符
懒惰则是匹配尽可能少的字符
下面的表格为懒惰限定符
代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或者多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
举个例子:
a.*b
在匹配aabab时会匹配到aabab
而a.*?b
在匹配aabab时会匹配到aab
和ab
这就说明*?
会在整个正则匹配成功的前提下使用最少的重复!
(在正则表达式中有一条规则,最先开始的匹配拥有最高的优先权
The match that begins earliest wins
)
总结
正则的简单学习花了很多时间,不知道值不值得,但还是有许多疏漏的地方,在学习python和javascript的时候再根据语言本身看看。