这这个教程中,我尝试使用一种故事场景来讲解正则表达式,以期能够为大家更愉快、更容易地理解。

有一天,小明在用Python做文本清洗的时候,突然觉得“233”太多了,觉得像是在嘲讽自己,怎么都看不顺眼,下定决心要把文本里所有的“233”全部干掉,于是一场属于小明和233的战争就这样拉开了帷幕。

一开始,小明直接使用制式武器字符串替换,通过将“233”替换为空,处理掉了所有的“233”。

str = str.replace("233", "", str)

(以上代码的打击面是字符串中包含的所有“233”子字符串)

当他码放南山、刀枪入库准备收工的时候,发现“233”居然还有无计其数的同盟,他们有“2333”、“23333”、“233333”……于是,小明取出了无差别平A碾压攻击武器正则表达式,准备再次向“233”的所有同盟开火,将这一类的“233”的同盟全部消灭。

str = re.sub("23+", "", str)

(以上代码的打击面是字符串中的“23”、“233”、“2333”、“23333”……)

通过这个应用,我们可以简单地将正则表达式理解为一种用来描述字符串的字符串(文本模式)。一个正则表达式可以同时对应很多个字符串,例如在上例中,正则表达式"23+"就可以同时对应“233”、“2333“、”23333”……等等。

我们将检查字符串中是否有可以对应正则表达式的子字符串的过程,称之为匹配

在上例所使用的正则表达式中,“+”表示将前一个字符匹配一次或多次,因此“23+”可以匹配“23”、“233”、“2333”……。在正则表达式中,我们将类似“+”的用来描述匹配要求的字符,称之为元字符。其中,“+”这类描述匹配次数的元字符,称为限制符

类似的,在正则表达式中还有“*”、“+”也用来表示对前一个字符的匹配次数。这类元字符的具体描述如下:




Android 正则表达式移去掉无用的标签_字符串

表示匹配次数的元字符(一)



但是小明突然发现,刚才的无差别平A碾压攻击如果执行,将会误伤了己方卧底“23”。本着不放弃任何一个兄弟的原则,小明修改了无差别平A碾压攻击武器,将”23”从目标范围中剔除。

str = re.sub("23{2,}", "", str)

(以上代码的打击面是字符串中的“233”、“2333”、“23333”……,但不包括“23”)

在上例所使用的正则表达式中,“{2,}”表示至少匹配2次,因此“23{2,}”可以匹配“233”、“2333“但不能匹配”23“。类似的元字符还有”{n}“、”{n,m}“。这类元字符的具体描述如下:




Android 正则表达式移去掉无用的标签_正则剔除非utf8_02

表示匹配次数的元字符(二)



就在小明即将按下“攻击”按钮时,卧底“23”发来消息,通过他潜伏字符串中间对“233”们的劝说,位于字符串中间的“233”的立场已经动摇了,现在可以确定的仍然是坏数只有在字符串开头和结尾的“233”,希望先将这些“233”消灭杀鸡儆猴。于是小明第一次启动了无差别平A碾压攻击武器,消灭了字符串开头和结尾的“233”族群。

str = re.sub("^23{2,}|23{2,}$", "", str)

(以上代码的打击面是位于字符串开头或结尾位置的“233”、“2333”、“23333”……)

在上例所使用的正则表达式中,”^“表示匹配字符串开头、”$“表示匹配字符串结尾,”|“表示或的意思,因此可以同时匹配在字符串开头和结尾处的“233”。这类元字符的具体描述如下(请特别注意”|“的影响范围):




Android 正则表达式移去掉无用的标签_元字符_03

元字符:匹配字符串头尾、或



在这一轮的攻击之后,”233“族群虽然受到重创,但是其中的一些“233”却选择了反击。公然打出”小明2333“、”233小明“等标语嘲讽小明,小明不堪其扰;但是因为卧底“23”仍然在劝降,不希望打击面过广。因此,小明决定针对这些点名嘲讽小明的“233”采取定点打击,同时在打击过程中不能误伤到自己的名字。

str = re.sub("(?<=小明)23{2,}|23{2,}(?=小明)", "", str)

(以上代码的打击面是:“小明233”中的“233“,”233小明“中的”233“……)

在上例使用的正则表达式中:

“(?<=小明)”表示在匹配到的“233”应该是紧接着”小明“之后的,不要前面没有”小明“的”233“;但是匹配的结果只是”233“自己,前面的“小明”只是判断条件,并不匹配到结果中。

而“(?=小明)”则表示在已经匹配到的“233”之后应该紧跟着“小明”,不要后面没有“小明”的“233”;但是匹配的结果也只有“233”自己,后面的“小明”也只是判断条件,并不匹配到结果中。

这种不匹配到结果,而是用作判断条件的匹配我们称之为非获取匹配。非获取匹配的具体描述如下:




Android 正则表达式移去掉无用的标签_字符串_04

元字符:非获取匹配



在这一次的定点打击后,剩余的大部分”233“都成功被卧底“23”劝降了,改成”666“不再嘲讽小明了。但是其中还是有一些”233“开始隐藏自己,有的改成了”2A333“,有的改成了”2c333“,为了进一步消灭这些”233“,小明再一次调整 了无差别平A碾压攻击武器,对这类”233“进行打击。

str = re.sub("2[A-Za-z]?3{2,}", "", str)

(以上代码的打击面包括“2A33”、“2a33”、“2B33”、“2b33”……)

在上例使用的正则表达式中,“[A-za-z]”表示匹配任意大写或小写的英文字母,因此可以匹配在“2”和“33”之间添加了英文字母的”233“。类似的元字符具体描述如下:




Android 正则表达式移去掉无用的标签_正则剔除非utf8_05

元字符:字符集合和字符范围



其中同一个方括号中可以包含多个字符范围,例如“[A-Za-z]”,其中的每一个字符范围都能起作用。

经过这一次的打击,仍然隐藏着的“233”非常恐惧,他们选择进一步隐藏自己,将制表符、换行符之类不可见的字符加到了2和3之间,形成了诸如“233”、“233”之类深度变形的“233”。但是小明作为不懂英文还可以和外国笔友交流的优秀学生,决定对这些利用不可见字符深度隐藏的“233”进行打击。

str = re.sub("2s?3{2,}", "", str)

(以上代码的打击面包括“233”、“233”、“233”……)

在正则表达式中,“”表示转义,即多种编程语言中转移字符的概念。例如“”匹配“”;“”匹配“”,而“”则匹配换行符。转义符及其他通过转义符表示的字符的详细描述如下:




Android 正则表达式移去掉无用的标签_元字符_06

元字符:转义符号



在上例使用的表达式中,“s”表示匹配任意不可见字符,包括空格、换行符、制表符等;这是一个通配符,等价于”[ fv]“。其他通配符的详细描述如下:




Android 正则表达式移去掉无用的标签_字符串_07

元字符:通配符



经过多次对“233”的打击,“233"向小明表示无条件投降,并将所有“233”均改为“666”,小明与“233”的战争终于结束。

至此,绝大多数的正则表达式元字符都已经被介绍过了,这些元字符在学习之初并不需要全部都记下来,可以在使用的时候查表,多用用慢慢就熟了。