正则表达式入门 Surager

最简单的正则表达式

在一段文本中查找high这个单词的时候,high这个单词就是个正则表达式。它可以精确匹配到以下内容:由四个字符组成,第一个是h,后面依次是igh。如果要精确查找到high这个单词,则需要写:\bhigh\b

\b被称作一个元字符。代表单词单词的开头结尾或者是分界处。只匹配一个位置。

你想查找high后面不远处有个”到不行了”,应该用\bhigh\b.*\b到不行了\b

这里的.是一个元字符,代表除了换行符之外的任意一个字符。*也是一个元字符,代表*前边的内容可以重复多次以使得整个表达式得到匹配

所以,\bhigh\b.*\b到不行了\b的意思就是先是一个单词high,然后是任意个非换行符的字符,最后是到不行了这四个字

0\d\d-\d\d\d\d\d\d\d\d,这个表达式表示以0开头,然后是两个数字,然后是一个连接符“-”,然后是8个数字。

\d是一个元字符,匹配一个数字。

为了避免重复,我们可以这么写:0\d{2}-\d{8}

更多的元字符

代码说明
.匹配除换行符以外的任意字符
\w匹配字母或数字或下划线或汉字
\s匹配任意的空白符
\d匹配数字
\b匹配单词的开始或结束
^匹配字符串的开始
$匹配字符串的结束

\s匹配空白符,包括空格,制表符,换行符,中文全角空格等。

\w匹配字母或数字或下划线或汉字。

example:

  • \ba\w*\b,以a开头的单词a后有任意数量的字符或数字
  • \d+,匹配一个或者多个数字。+和*差不多,*可以是0次,+至少一次。
  • \b\w{6}\b六个字符组成的单词

\^和$匹配一个字符串的开头和结尾。例如输入的QQ号必须为5位到12位时,可以写^\d{5,12}$,其中{5,12}表示不多于12,不少于5。

字符转义

查找.的话可以写\.

重复

代码/语法说明
*重复零次或更多次
+重复一次或更多次
?重复零次或一次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次

字符类

预定义:[.?!]代表匹配点号或者问好或者叹号。[aeiou]匹配元音字母。

[0-9]与\d功能一致,[a-z0-9A-Z_]与\w功能一致(只考虑英文)。

一个例子:\(?0\d{2}[)-]?\d{8}

这个正则表达式可以匹配几种格式的电话号码,像(010)88886666,或者022-22334455,或者011-45141145

分枝条件

0\d{2}-\d{8}|0\d{3}-\d{7}表示-前三位,-后八位,或者-前四位,-后七位。

匹配成功的案例:010-12345678,0536-1234567。

分组

(\d{1,3}\.){3}\d{1,3}用来匹配一个ip地址,表示三位数字加一个点的组合重复三次再匹配三位数字,但是无法检测合法性。例如:256.300.888.999

((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)这个正则表达式虽然很长却可以检测正确性。

反义

代码/语法说明
\W匹配任意不是字母,数字,下划线,汉字的字符
\S匹配任意不是空白符的字符
\D匹配任意非数字的字符
\B匹配不是单词开头或结束的位置
[^x]匹配除了x以外的任意字符
[^aeiou]匹配除了aeiou这几个字母以外的任意字符

就是大写或者加上^符来不匹配某些字符。

后向引用

\b(\w+)\b\s+\1\b,其中(\w+)建立了一个组,并且命名为分组1,后面的\1表示(\w+),而且两个内容一样。比如go go,jo jo这样的单词。

\b(?<Word>\w+)\b\s+\k<Word>\b,自己规定组名。

分类代码/语法说明
 (exp)匹配exp,并捕获文本到自动命名的组里
捕获(?exp)匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)
 (?:exp)匹配exp,不捕获匹配的文本,也不给此分组分配组号
 (?=exp)匹配exp前面的位置
零宽断言(?<=exp)匹配exp后面的位置
 (?!exp)匹配后面跟的不是exp的位置
 (?<!exp)匹配前面不是exp的位置
注释(?#comment)这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

零宽断言

(?=exp)也叫零宽度正预测先行断言,断言自身出现的位置的后面能匹配表达式exp。

\b\w+(?=ing\b)匹配以ing结尾的单词ing前面的部分。I’m singing while you’re dancing,匹配结果为sing,danc。

(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。

(?<=\bre)\w+\b匹配以re开头re以后的部分,reading a book匹配结果为ading。

((?<=\d)\d{3})+\b对1234567890匹配,匹配结果为234567890。

原因是表达式最后有一个\b,所以从结尾开始数,数三位数,前面的那一位就不匹配,然后再从刚刚位置往前数,当前面不满三位数的时候停止。所以三次分别数出890、567、234。

负向零宽断言

零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。

\d{3}(?!\d)匹配三位不以数字为后续的数字,\b((?!abc)\w)+\b匹配没有abc这个连续字符串的单词。

零宽度负回顾后发断言(?<!exp),断言此位置的前面不能匹配表达式exp。

(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。

(?<=<(\w+)>).*(?=<\/\1>)匹配html里面的标签。例如<b></b>这样的。

注释

通过(?#comment)来包含注释。

2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)

我们可以前面的一个表达式写成这样:

     (?<=    # 断言要匹配的文本的前缀
      <(\w+)> # 查找尖括号括起来的内容
              # (即HTML/XML标签)
      )       # 前缀结束
      .*      # 匹配任意文本
      (?=     # 断言要匹配的文本的后缀
      <\/\1>  # 查找尖括号括起来的内容
              # 查找尖括号括起来的内容
      )       # 后缀结束

贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。

a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。

代码/语法说明
*?重复任意次,但尽可能少重复
+?重复1次或更多次,但尽可能少重复
??重复0次或1次,但尽可能少重复
{n,m}?重复n到m次,但尽可能少重复
{n,}?重复n次以上,但尽可能少重复

平衡组/递归匹配

xx <aa <bbb> <bbb> aa> yy这样的字符串匹配,其中尖括号是嵌套的。

  • (?’group’) 把捕获的内容命名为group,并压入堆栈(Stack)
  • (?’-group’) 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
  • (?(group)yesno) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
  • (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败
<                   #最外层的左括号
  [^<>]*            #它后面非括号的内容
  (
      (
        (?'Open'<)  #左括号,压入"Open"
        [^<>]*      #左括号后面的内容
      )+
      (
        (?'-Open'>) #右括号,弹出一个"Open"
        [^<>]*      #右括号后面的内容
      )+
  )*
  (?(Open)(?!))     #最外层的右括号前检查
                    #若还有未弹出的"Open"
                    #则匹配失败

>                #最外层的右括号

衡组的一个最常见的应用就是匹配HTML,下面这个例子可以匹配嵌套的<div>标签:<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>

在python中的引用

Python提供了re模块来支持正则表达式相关操作

函数说明
compile(pattern, flags=0)编译正则表达式返回正则表达式对象
match(pattern, string, flags=0)用正则表达式匹配字符串 成功返回匹配对象 否则返回None
search(pattern, string, flags=0)搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None
split(pattern, string, maxsplit=0, flags=0)用正则表达式指定的模式分隔符拆分字符串 返回列表
sub(pattern, repl, string, count=0, flags=0)用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数
fullmatch(pattern, string, flags=0)match函数的完全匹配(从字符串开头到结尾)版本
findall(pattern, string, flags=0)查找字符串所有与正则表达式匹配的模式 返回字符串的列表
finditer(pattern, string, flags=0)查找字符串所有与正则表达式匹配的模式 返回一个迭代器
purge()清除隐式编译的正则表达式的缓存
re.I / re.IGNORECASE忽略大小写匹配标记
re.M / re.MULTILINE多行匹配标记

例子:

pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)')
sentence = '''
    重要的事情说8130123456789遍,我的手机号是13512346789这个靓号,
    不是15600998765,也是110或119,王大锤的手机号才是15600998765。
    '''
# 1.
mylist = re.findall(pattern, sentence)
print(mylist)
# 2.
for temp in pattern.finditer(sentence):
	print(temp.group())
# 3.
m = pattern.search(sentence)
while m:
    print(m.group())
    m = pattern.search(sentence, m.end())
	
sentence = '你丫是傻叉吗? 我操你大爷的. Fuck you.'
purified = re.sub('[操肏艹草曹]|fuck|shit|傻[比屄逼叉缺吊屌]|煞笔','*', sentence, flags=re.IGNORECASE)
username = input('请输入用户名: ')
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username)
poem = '窗前明月光,疑是地上霜。举头望明月,低头思故乡。'
sentence_list = re.split(r'[,。, .]', poem)
while '' in sentence_list:
	sentence_list.remove('')