简单粗暴正则表达式

在工作和学习中我时不时会使用正则表达式处理一些文本分析任务,但仍觉得自己是个新手,熟手也算不上。但从现在开始,我想做个这方面的能手,写这篇文章即为开始,也希望能帮助到需要的朋友。写了这篇文章也不意味着我就成能手了,要专于此,路且长漫。俗话说,共欲善其事必先利其器,正则表达式就是这样一个值得利的工具,这篇文章试图提供一个短小简练的正则表达式参考手册。

目录

什么是正则表达式

正则表达式(简称为regex)是一个由字符和特殊符号组成的字符串模式,它描述了一系列符合某个句法规则的字符串。正则表达式中的特殊符号称之为元字符。以下举几个例子来说明。

搜索 v.s. 匹配

匹配指的是判断一个字符串能否从开始位置全部或者部分地匹配某个模式。 搜索指的是在目标字符串的任意位置开始搜索匹配的模式。

正则表达式模式 匹配的字符串
foo foo
Python Python
abc123 abc123

正则表达式有什么用

正则表达式是用来处理字符串的,想知道其有何用,不妨看看它的应用:

  • 许多文本编辑器、IDEs支持以正则表达式进行查找和替换
  • 许多编程语言支持正则表达式,如C++,Perl,Python,SQL
在Visual Studio Code中查找本文档里包含的日期

正则表达式的构成

前面已经提到正则表达式由字符和特殊符号组成。

  • 符号
表示法 描述 正则表达式示例
literal 匹配文本字符串中的字面值literal foo
re1|re2 匹配正则表达式re1或者re2 foo|bar
. 匹配任何字符(除了\n b.b
^ 匹配字符串开始部分 ^Dear
$ 匹配字符串终止部分 /bin/.*sh$
* 匹配0次或者多次前面的正则表达式 [A-Za-z0-9]*
+ 匹配1次或者多次前面的正则表达式 [a-z]+\.com
? 匹配0次或者1次前面的正则表达式 goo?
{n} 匹配n次前面的正则表达式 [0-9]{3}
{m,n} 匹配m~n次前面的正则表达式 [0-9]{5,9}
[...] 匹配来自字符集的任意单一字符 [aeiou]
[..x-y..] 匹配x~y范围中的任意单一字符 [0-9], [a-zA-Z]
[^...] 不匹配来自字符集的任一字符 [^aeiou], [^0-9A-Za-z]
(*|+|?{})? 非贪婪版本 .*?[a-z]
(...) 匹配封闭的正则表达式,然后另存为子组 ([0-9]{3})?, f(oo|u)bar
  • 特殊符号

以下特殊字符(前四个)的大写形式对应相反的含义。

表示法 描述 正则表达式示例
\d 等同于[0-9] data\d+.txt
\w 等同于[0-9a-zA-Z_] [A-Za-z]\w+
\s 等同于[\n\t\r\v\f] of\sthe
\b 匹配任何单词边界 \bThe\b
\N 反向引用已保存的子组 price;\1
\c 逐字匹配任何特殊字符c \.,\\,\*
\A(\Z) 等同于^($) \ADear
  • 扩展表示法
表示法 描述 正则表达式示例
(?iLmsux) 在正则表达式中嵌入一个或多个特殊“标记”参数 (?x), (? im)
(?:...) 表示一个匹配不用保存的分组 (?:\w+\.)*
(?P<name>...) 表示仅由name标识而不是数字ID标识的正则分组匹配 (?P<data>)
(?P=name) 在同一个字符串中匹配由(?P<name>)分组的之前的文本 (?P=data)
(?#...) 表示注释 (?#comment)
(?=...) 零宽度正预测先行断言 (?=\.com)
(?!...) 零宽度负预测先行断言 (?!\.net)
(?<=...) 零宽度正回顾后发断言 (?<=800-)
(?<!...) 零宽度负回顾后发断言 (?<!192\.168\.)
(?(id/name)Y|N) 如果分组提供的id或者name存在,就返回正则表达式的条件匹配Y,否则返回N (?x), (? im)

需要做进一步解释的正则表达式

在上一部分,我们了解了正则表达式的构成,其中有些需要做进一步的解释。

特殊标记嵌入

除了在re.compile中传递flags参数,我们可以利用(?iLmsux)来嵌入特殊标记。以下举几个例子:

>>> re.findall(r'(?i)yes', 'yes? Yes. YES!!')
['yes', 'Yes', 'YES']

在这个例子中,通过(?i)实现了不区分大小写的正则搜索。

再看一个支持多行并且不区分大小写的正则搜索例子:

>>> re.findall(r'(?im)^th[\w ]+', """
... This line is the first,
... another line,
... that line, it's the best
...""")
['This line is the first', 'that line']

零宽度正预测先行断言

零宽度正预测先行断言即(?=...)。举个例子加以说明。

对正则表达式(?=\.com)而言,如果一个字符串后面跟着.com才做匹配操作,否则不进行。

>>> re.findall(r'(?m)\w+(?=\.com)', """
... https://www.google.com
... https://example.net
... """)
['google']
仅匹配后面跟着.com的字符串

零宽度负预测先行断言

零宽度负预测先行断言即(?!...)。举个例子加以说明。

对正则表达式\b(?!un)\w+\b而言,如果一个单词的开始后面没有跟着un才做匹配操作,否则不进行。

>>> re.findall(r'\b(?!un)\w+\b', 'unsure sure unity used')
['sure', 'used']
仅匹配单词开始后没有跟着un的字符串

零宽度正回顾后发断言

零宽度正回顾后发断言即(?<=...)。举个例子以说明。

对于正则表达式(?<=19)\d{2}\b而言,如果某个四位数字以19开始,则进行匹配,否则不进行。

>>> re.findall(r'(?<=19)\d{2}\b', '1851 1999 1950 1905 2003')
['99', '50', '05']
仅对以19开始的四位数字进行匹配操作

零宽度负回顾后发断言

零宽度负回顾后发断言即(?<!...)。举个例子以说明。

对于正则表达式(?<!19)\d{2}\b而言,如果某个四位数字不以19开始,则进行匹配,否则不进行。

>>> re.findall(r'(?<=19)\d{2}\b', '1851 1999 1950 1905 2003')
['51', '03']
仅对不以19开始的四位数字进行匹配操作

正则表达式实例

手机号码匹配

手机号码校验是一个十分常见的应用场景,利用正则表达式可以很好地应对这个任务。要写出这样一个目的的正则表达式,我们需要先了解手机号码的构成情况。

中国境内的手机号码由三家移动运营商提供,即中国移动,中国联通和中国电信。手机号码由11位的数字组成,第一位是1,可以根据前三位来区分运营商,但现在由于携号转网的支持(2019年12月1日开始),该规则将不再适用。

手机卡 - 基础运营商

  • 支持语音通话 / 短信 / 数据流量
  • 号码长度 11 位
运营商1 号段
中国移动 134-0~8 / 135 / 136 / 137 / 138 / 139 / 150 / 151 / 152 / 157 / 158 / 159 / 172 / 178 / 182 / 183 / 184 / 187 / 188 / 195 / 197 / 198
中国联通 130 / 131 / 132 / 155 / 156 / 166 / 175 / 176 / 185 / 186 / 196
中国电信 133 / 134-9 / 153 / 173 / 174-00~05 / 177 / 180 / 181 / 189 / 190 / 191 / 193 / 199
中国广电 192
北京船舶通信导航有限公司(海事卫星通信) 174-9
工业和信息化部应急通信保障中心(应急通信) 174-06~12

手机卡 - 虚拟运营商

  • 支持语音通话 / 短信 / 数据流量
  • 号码长度 11 位
运营商1 号段
中国移动 165 / 1703 / 1705 / 1706
中国联通 167 / 1704 / 1707 / 1708 / 1709 / 171
中国电信 162 / 1700 / 1701 / 1702

物联网数据卡

  • 支持数据流量
  • 号码长度 13 位
运营商1 号段
中国移动 1440X / 148XX
中国联通 146XX
中国电信 1410X

上网卡

  • 支持语音通话(部分) / 短信 / 数据流量
  • 号码长度 11 位
运营商1 号段 语音通话2
中国移动 147 支持
中国联通 145 不支持
中国电信 149 支持

手机号码正则表达式

基于前面的信息,我们可以写一个能够匹配所有手机号的正则表达式:

^(?:\+?86)?1(?:[38]\d{3}|5[^4\D]\d{2}|7(?:[235-8]\d{2}|4(?:[09]\d|1[0-2]))|9[0-35-9]\d{2}|66\d{2})\d{6}$

以上正则表达式的可视化3如下:

中国手机号码正则表达式

参考链接


  1. 由于携号转网在部分地区已经试行,对于成功进行携号转网的用户,手机号段不再能体现其当前所属运营商。 ↩︎

  2. 根据工信部相关文件,145 / 147 / 149 号段允许提供语音通话功能,运营商可以根据用户需要自主决定是否提供语音通话功能。目前 147 / 149 号段已经有支持语音通话的号码卡放出。 ↩︎

  3. 可视化由网站https://www.debuggex.com/生成。 ↩︎

Avatar
杜利强
算法工程师

程序员、作家。

comments powered by Disqus

相关