正则表达式学习

定义

正则表达式就是记录文本规则的代码。

正则表达式测试

JavaScript正则表达式在线测试工具

Python正则表达式在线测试工具

元字符

常用元字符:

代码 说明
. 匹配除换行符外的任意字符
\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时会匹配到aabab

这就说明*?会在整个正则匹配成功的前提下使用最少的重复!

(在正则表达式中有一条规则,最先开始的匹配拥有最高的优先权

The match that begins earliest wins

总结

正则的简单学习花了很多时间,不知道值不值得,但还是有许多疏漏的地方,在学习python和javascript的时候再根据语言本身看看。