正则表达式的编写规则

使用正则表达式可以实现较复杂的文本搜索和替换。本节介绍正则表达式的各种编写规则,这部分内容是正则表达式的核心内容,在不同计算机语言中,这部分内容基本上是相同的。[大谦Excel,dqexcel点com]

元字符

元字符是在正则表达式中具有特殊含义的字符,其含义超出了自己本身的含义。比如Python正则表达式中,用\d表示数字,用\s表示空白。常见的元字符如表8-2中所示。

表8-2 常见元字符

元字符 说 明 元字符 说 明
. 匹配除换行符以外的任意字符 ^ 匹配字符串的开始
\w 匹配字母、数字、下划线或汉字 $ 匹配字符串的结束
\s 匹配任意空白符 \n 匹配一个换行符
\d 匹配数字 \r 匹配一个回车符
\b 匹配单词的开始或结束 \t 匹配一个制表符

一般情况下是指定要查找的字符或在指定的范围内进行查找,但有时情况会反过来,即排除指定的字符或在指定的字符范围之外进行查找。这种情况下使用表示反义的元字符,如用\D表示非数字的字符,用\S表示非空白的字符。常见的反义元字符如表8-3中所示。

表8-3 反义元字符

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

下面结合一些示例来深入理解元字符。

给定原始字符串"BC_101PW%",查找其中的数字,并将数字替换为空,即删除。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\元字符.xlsm。正则表达式"\d"表示单个的数字。

code.vba
Sub Sam01()
  Dim objReg As New RegExp  '输出正则对象
  Dim strT As String
  Dim mcT As MatchCollection  '存放全部查找结果
  Dim matT As Match  '单个查找结果
  strT = "BC_101PW%"  '给定字符串
  With objReg
    .Global = True  '全局
    .Pattern = "\d"  '匹配单个数字
    Set mcT = .Execute(strT)  '执行查找,结果返回到集合
    Debug.Print .Replace(strT, "")  '查找结果替换为空
  End With
  For Each matT In mcT  '遍历每个查找结果
    If matT <> " " Then Debug.Print matT.Value  '输出到立即窗口
  Next matT
End Sub

运行过程,在立即窗口中输出替换和查找结果。

code.vba
BC_PW%
1
0
1

【Python】

下面给定原始字符串,用re.findall函数查找其中的全部数字,用re.sub函数将所有数字替换为空。单个的数字用元字符\d表示。

在Python Shell窗口输入:

code.python
>>> import re
>>> a="BC_101PW%"  #原始字符串
>>> m0=re.findall(r"\d&quot;,a)  #查找所有数字
>>> m0
['1', '0', '1']
>>> for i in m0:  #逐个输出数字
		print(i)
1
0
1
>>> ms=re.sub(r"\d&quot;,"",a)  #所有数字替换为空(删除)
>>> ms
'BC_PW%'

下面的示例测试元字符\b,它表示单词的开头或结尾。正则表达式为r"\bC\d",表示匹配字符串必须是原始字符串以C打头或C前面为空格,C的后面跟数字。匹配的字符串置换为空。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\元字符.xlsm。

code.vba
Sub Sam02()
  Dim objReg As New RegExp  '输出正则对象
  Dim strT As String
  strT = "C5dC56 C5"  '给定字符串
  With objReg
    .Global = True  '全局匹配
    .Pattern = "\bC\d"  '正则表达式
    Debug.Print .Replace(strT, "")  '将匹配结果置换为空
  End With
End Sub

运行过程,在立即窗口输出结果:

code.vba
dC56

因为第1个C5位于原始字符串的开头,满足C加数字的条件,匹配;第2个C5前面为空格,满足\b的条件。将它们置换为空后剩下的字符串即为'dC56 '。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="C5dC56 C5"
>>> m=re.sub(r"\bC\d","",a)
>>> m
'dC56 '

元字符^限制字符在原始字符串的最前面,如^\d表示原始字符串以数字打头。下面给定原始字符串,如果它以一个以上的数字打头,返回该数字。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\元字符.xlsm。

code.vba
Sub Sam03()
  '省略部分代码
  '…
  strT = "12345my09"  '给定字符串
  With objReg
    .Global = True  '全局
    .Pattern = "^\d+"  '正则表达式
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历集合
      Debug.Print matT  '输出每个查找结果
    Next matT
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
12345

因为12345位于原始字符串打头位置,匹配;而09虽然也是数字,但不在打头位置,不匹配。正则表达式中的加号是表示重复的元字符,前面为d,表示一个以上的数字。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="12345my09"
>>> m=re.findall(r"^\d+",a)
>>> for i in m:
		print(i)
12345

下面的代码中,\D表示不是数字的字符,元字符$限制字符在原始字符串的结尾处,如C$表示最后一个字符是C。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\元字符.xlsm。

code.vba
Sub Sam04()
  '省略部分代码
  '…
  strT = "m12345my09W"  '给定的字符串
  With objReg
    .Global = True  '全局
    .Pattern = "\d+\D"  '正则表达式,前面是数字,后面是非数字
    '.Pattern = "\d+\D$"  '正则表达式,匹配字符串必须位于结尾处
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历集合
      Debug.Print matT  '输出结果
    Next matT
  End With
End Sub

运行过程,在立即窗口中输出查找结果。

code.vba
12345m
09W

第1个正则表达式r"\d+\D"表示前面是1个以上的数字,后面跟的字符不是数字。

使用第2个正则表达式时运行过程,在立即窗口输出:

09W

第2个正则表达式r"\d+\D$"在最后面添加了"$",表示匹配的字符串必须位于原始字符串的结尾处,所以只匹配到"09W"。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="12345my09W"
>>> m=re.findall(r"\d+\D",a)
>>> m
['12345m', '09W']
>>> m=re.findall(r"\d+\D$",a)
>>> m
['09W']

重复

进行查找或替换时有时需要连续查找或替换多个某种类型的字符,这就是重复。重复次数可以是确定的,也可以是不确定的。比如Python正则表达式中用\d+表示1个以上的数字,重复次数不确定;\d{5}表示5个数字,重复次数是确定的。

Python正则表达式中表示重复的元字符如表8-4中所示。

表8-4 表示重复的元字符

元字符 说 明 元字符 说 明
* 重复零次或更多次 {n} 重复n次
+ 重复一次或更多次 {n,} 重复n次或更多次
? 重复零次或一次 {n,m} 重复n到m次

元字符*表示前面定义的字符可以重复0次或任意次,相当于 {0,}。下面给定1个字符串,找出所有W打头,后面跟0个或0个以上数字的子字符串。

下面查找给定字符串中所有以W打头,后面跟0个或0个以上数字的子字符串。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\重复.xlsm。

code.vba
Sub Sam08()
  '省略部分代码
  '…
  strT = "W123YZW85CW0DFWU"  '给定的字符串
  With objReg
    .Global = True  '全局
    .Pattern = "W\d*"  '正则表达式,以W打头,后面跟0个或0个以上数字
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历查找到的结果
      Debug.Print matT  '输出结果
    Next matT
  End With
End Sub

运行过程,在立即窗口输出结果:

code.vba
W123
W85
W0
W

注意最后1个元素在W后面没有跟数字。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="W123YZW85CW0DFWU"
>>> m=re.findall(r"W\d*",a)
>>> m
['W123', 'W85', 'W0', 'W']

元字符+表示前面定义的字符可以重复1次或任意次,相当于 {1,}。下面给定1个字符串,找出所有W打头,后面跟1个或1个以上数字的子字符串。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\重复.xlsm。

code.vba
Sub Sam081()
  '省略部分代码
  '…
  strT = "W123YZW85CW0DFWU"  '给定的字符串
  With objReg
    .Global = True  '全局
    .Pattern = "W\d+"  '正则表达式,W打头,后面跟1个或1个以上数字
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历查找结果
      Debug.Print matT  '输出
    Next matT
  End With
End Sub

运行过程,在立即窗口中输出匹配结果。

code.vba
W123
W85
W0

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="W123YZW85CW0DFWU"
>>> m=re.findall(r"W\d+",a)
>>> m
['W123', 'W85', 'W0']

元字符?表示前面定义的字符可以重复0次或1次,相当于{0,1}。下面给定1个字符串,找出所有前后都是数字,中间有或没有小数点的子字符串。

下面在给定字符串中查找带或不带小数点的数字。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\重复.xlsm。

code.vba
Sub Sam09()
  '省略部分代码
  '…
  strT = "W10.23RWA908C5..1"  '给定字符串
  With objReg
    .Global = True  '全局查找
    .Pattern = "\d+\.?\d+"  '正则表达式,数字带或不带小数点
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历查找结果
      Debug.Print matT  '输出结果
    Next matT
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
10.23
908

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="W10.23RWA908C5..1"
>>> m=re.findall(r"\d+\.?\d+",a)
>>> m
['10.23', '908']

使用{}可以设置重复次数。{n}表示前面定义的字符重复n次。下面给定1个字符串,找出其中连续3个都是数字的子字符串。

下面从给定字符串中删除连续出现3个数字的子字符串。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\重复.xlsm。

code.vba
Sub Sam10()
   Dim objReg As New RegExp
   Dim strT As String
   strT = "WT123Pq89C"  '给定字符串
   With objReg
     .Global = True  '全局
     .Pattern = "\d{3}"  '正则表达式,连续3个数字
     Debug.Print .Replace(strT, "")  '删除匹配的子字符串
   End With
 End Sub

运行过程,在立即窗口中输出结果:

code.vba
WTPq89C

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a='WT123Pq89C'
>>> m=re.findall(r"\d{3}",a)
>>> re.sub(r'\d{3}','',a)
'WTPq89C'

{m,n}表示前面定义的字符的重复次数在1个指定的范围内取值,最小重复m次,最多重复n次。下面给定1个字符串,找出其中连续2个或3个都是数字的子字符串。

下面从给定的字符串中删除连续2个或3个为数字的子字符串。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\重复.xlsm。

code.vba
Sub Sam11()
   Dim objReg As New RegExp  '输出正则对象
   Dim strT As String
   strT = "WT123Pq89C"  '给定的字符串
   With objReg
     .Global = True  '全局
     .Pattern = "\d{2,3}"  '正则表达式,连续2个或3个数字
     Debug.Print .Replace(strT, "")  '删除匹配子字符串
   End With
 End Sub

运行过程,在立即窗口中输出结果:

code.vba
WTPqC

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="WT123Pq89C"
>>> re.sub(r'\d{2,3}','',a)
 'WTPqC'

{m,}表示前面定义的字符最少重复m次,相当于元字符+。下面给定1个字符串,找出其中连续2个以上都是数字的子字符串。

下面从给定字符串中删除为连续2个和2个以上数字的子字符串。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\重复.xlsm。

code.vba
Sub Sam12()
  Dim objReg As New RegExp  '输出正则对象
  Dim strT As String
  strT = "WT123Pq89C"  '给定字符串
  With objReg
    .Global = True  '全局
    .Pattern = "\d{2,}"  '正则表达式,连续2个或2个以上数字
    Debug.Print .Replace(strT, "")  '删除匹配子字符串
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
WTPqC

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="WT123Pq89C"
>>> re.sub(r'\d{2,}','',a)
 'WTPqC'

字符类

使用前面的方法可以查找指定的数字、字母、数字或空白,但是如果给定的是一个字符集,要求查找的字符只在这个集合中取或者在这个集合外取,就要用到中括号。中括号定义字符集的方式如表8-5中所示。

表8-5 中括号的用法

应用格式示例 说 明
[adwkf] 查找的字符是中括号内字符中的一个
[^adwkf] 查找的字符不是中括号内字符的就行
[b-f] 查找的字符是b到f中的一个
[^b-f] 查找的字符不是b到f中的一个
[2-5] 查找的字符是2到5中的一个
[2-46-9] 查找的字符是2到4或6到9中的一个
[a-w2-5A-W] 查找的字符是a到w, 2到5或A到W范围内的一个
[^一-龥]或[^\u4e00-\u9fa5] 查找的字符是中文字符

使用方括号[]包含个字符集,能够匹配其中任意1个字符。使用[^],则不匹配方括号内的字符,只能匹配该字符集之外的任意1个字符。

下面给定1个字符串,找出字符串中与方括号中任意字符匹配的字符。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\字符类.xlsm。

code.vba
Sub Sam15()
   Dim objReg As New RegExp  '生成正则对象
   Dim strT As String
   strT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  '给定字符串
   With objReg
     .Global = True
     .Pattern = "[AEIOU]"  '正则表达式,与中括号中的任意字符匹配
     Debug.Print .Replace(strT, "")  '删除匹配对象
   End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
BCDFGHJKLMNPQRSTVWXYZ

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> re.sub('[AEIOU]','',a)
'BCDFGHJKLMNPQRSTVWXYZ'

下面找出字符串中与方括号中任意字符不匹配的字符。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\字符类.xlsm。

code.vba
Sub Sam16()
   Dim objReg As New RegExp  '输出正则对象
   Dim strT As String
   strT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  '给定字符串
   With objReg
     .Global = True
     .Pattern = "[^AEIOU]"  '正则表达式,与中括号中字符不匹配
     Debug.Print .Replace(strT, "")  '删除匹配对象
   End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
AEIOU

【Python】

在Python Shell窗口输入:

code.python
>>> impoirt re
>>> a="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
>>> re.sub(r'[^AEIOU]','',a)
'AEIOU'

给定1个字符串,找出字符串中落在方括号中指定字符范围的字符。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\字符类.xlsm。

code.vba
Sub Sam17()
   Dim objReg As New RegExp
   Dim strT As String
   strT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  '给定字符串
   With objReg
     .Global = True
     .Pattern = "[G-T]"  '正则表达式,与给定范围内的字符匹配
     Debug.Print .Replace(strT, "")  '删除匹配对象
   End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
ABCDEFUVWXYZ

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
>>> re.sub(r'[G-T]','',a)
 'ABCDEFUVWXYZ'

给定1个字符串,找出字符串中1-5的数字和G-T的字母。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\字符类.xlsm。

code.vba
Sub Sam18()
   Dim objReg As New RegExp
   Dim strT As String
   strT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
   With objReg
     .Global = True
     .Pattern = "[1-5G-T]"  '正则表达式,匹配1-5和G-T范围内的字符
     Debug.Print .Replace(strT, "")  '删除匹配对象
   End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
ABCDEFUVWXYZ67890

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
>>> re.sub(r'[1-5G-T]','',a)
'ABCDEFUVWXYZ67890'

查找字符串中的汉字,正则表达式中用中括号指定汉字范围。可以有两种指定方式,即[一-龥]和[\u4e00-\u9fa5]。后一种方式是以四位十六进制数表示的Unicode字符。汉字一的编码是4e00,最后一个代码是9fa5。

下面给定一个包含汉字的字符串,找出其中的汉字,并将它们替换为""。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\字符类.xlsm。

code.vba
Sub Sam06()
    Dim objReg As New RegExp
    Dim strT As String
    strT = "123 中 hwo 文 tr89 字符"  '给定字符串
    With objReg
      .Global = True
      .Pattern = "[\u4e00-\u9fa5]"  '正则表达式,指定汉字范围进行匹配
      '.Pattern = "[一-龥]"  '用另外一种方式指定汉字范围
      Debug.Print .Replace(strT, "")  '删除匹配的汉字
    End With
 End Sub

运行过程,在立即窗口中输出结果:

code.vba
123  hwo  tr89

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="123 中 hwo 文 tr89 字符"
>>> m=re.findall("[\u4e00-\u9fa5]",a)
>>> m
['中', '文', '字', '符']
>>> m=re.sub("[\u4e00-\u9fa5]","",a)
>>> m
'123  hwo  tr89 '

分支条件

假设有几种规则,只要满足其中一种即可完成匹配,就要用到分支条件。使用 | 将不同的规则进行分隔。比如数字后面跟重量单位,有的记录为公斤,有的记录为千克,可以用"\d+(公斤|千克) "进行提取,相当于"\d+公斤|d+千克"。

下面给定的字符串中数字后面跟公斤、kg或千克,用分支条件编写正则表达式进行查找。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\元字符.xlsm。

code.vba
Sub Sam27()
  '省略部分代码
  '…
  strT = "10公斤 20kg 30千克"  '给定的字符串
  With objReg
    .Global = True
    .Pattern = "\d+(公斤|千克|kg)"  '正则表达式,数字后面跟单位
    '.Pattern = "\d+公斤|\d+千克|\d+kg"  '等价写法
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历查找结果
      Debug.Print matT  '输出结果
    Next
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
10公斤
20kg
30千克

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="10公斤 20kg 30千克"
>>> m=re.finditer(r"\d+(公斤|千克|kg)",a)
>>> for i in m:
		print(i.group(0))
10公斤
20kg
30千克

捕获分组和非捕获分组

正则表达式中存在有子表达式的情况,子表达式用小括号指定并作为一个整体进行操作。比如,下面代码中的正则表达式"((ABC){2})"将"ABC"作为一个整体重复2次。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\分组.xlsm。

code.vba
Sub Sam19()
   Dim objReg As New RegExp
   Dim strT As String
   strT = "ABCABCWTU238"  '给定的字符串
   With objReg
     .Global = True
     .Pattern = "((ABC){2})"  '正则表达式,"ABC"作为一个整体重复2次
     Debug.Print .Replace(strT, "")  '删除匹配对象
   End With
End Sub

运行过程,在立即窗口输出结果:

code.vba
WTU238

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="ABCABCWTU238"
>>> m=re.search("((ABC){2})",a)
>>> re.sub('((ABC){2})','',a)
'WTU238'

使用小括号对正则表达式进行分组时,会自动分配组号。分配组号的原则是从左到右,从外到内。使用组号可以对对应的分组进行反向引用。

下面的代码中,正则表达式r"(WT)\d+\1"匹配原始字符串中前后都是"WT",中间是1个或多个数字的子字符串。注意其中的\1表示小括号内的"WT",这个分组自动分配组号1,使用\1进行反向引用。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\分组.xlsm。

code.vba
Sub Sam20()
  '省略部分代码
  '…
  strT = "abcWT12389WT"  '给定的字符串
  With objReg
    .Global = True
    .Pattern = "(WT)\d+\1"  '正则表达式,两个WT中间是数字
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历查找结果
      Debug.Print matT  '输出结果
    Next
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
WT12389WT

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="abcWT12389WT"
>>> m=re.finditer(r"(WT)\d+\1",a)
>>> for i in m:
		print(i.group())
WT12389WT

匹配结果"WT12389WT "两端都是"WT",中间全是数字,满足匹配要求。

下面的示例演示有更多分组的情况。正则表达式r"((WT){2})((PR){2})\d+\2\4"中一共有4对小括号,前面两层后面两层,下面探查各小括号对应的分组的编号。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\分组.xlsm。

code.vba
Sub Sam21()
  '省略部分代码
  '…
  strT = "abWTWTPRPR123WTPR56"  '给定的字符串
  With objReg
    .Global = True
    .Pattern = "((WT){2})((PR){2})\d+\2\4"  '正则表达式
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历查找结果
      Debug.Print matT
      Debug.Print matT.SubMatches(0)  '输出匹配结果中的分组子字符串
      Debug.Print matT.SubMatches(1)
      Debug.Print matT.SubMatches(2)
      Debug.Print matT.SubMatches(3)
    Next
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
WTWTPRPR123WTPR
WTWT
WT
PRPR
PR

结果显示,按照从左到右的原则,首先给左边的两层小括号对应的分组编号,此时按照从外到内的顺序编号。外层小括号中为" (WT){2}",即匹配"WTWT";内存小括号中为"WT",它们对应于分组编号1和2。右边两层小括号的情况类似,匹配第3个分组"PRPR"和第4个分组"PR"。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="abWTWTPRPR123WTPR56"
>>> m=re.search(r"((WT){2})((PR){2})\d+\2\4",a)
>>> m.group(1)
'WTWT'
>>> m.group(2)
'WT'
>>> m.group(3)
'PRPR'
>>> m.group(4)
'PR'

上面用小括号定义的分组,每个分组都自动进行编号,并可以用Match对象的group方法进行捕获,匹配结果保存到内存,称为捕获分组。但有时候,我们并不关注匹配到的内容,即分组参与匹配,但没有必要进行捕获,不用在内存中保存匹配到的内容。此时仍然用小括号进行分组,但是在小括号里的最前端加上"?:",如(?:\d{3})。这种分组称为非捕获分组。非捕获分组不参与编号,不在内存保存匹配结果,所以能节省内存空间,提高工作效率。

下面给定一个原始字符串,正则表达式为r"(?:ab)(CD)\d+\1",其中两个分组,第1个分组在小括号内的最前端有"?:",为非捕获分组。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\分组.xlsm。

code.vba
Sub Sam22()
  Dim objReg As New RegExp
  Dim strT As String, mc, c
  Dim m As Match
  strT = "abCD123CDbc"  '给定字符串
  With objReg
    .Global = True
    .Pattern = "(?:ab)(CD)\d+\1"  '正则表达式
    Set mc = .Execute(strT)  '执行查找
    For Each m In mc  '遍历查找结果
      Debug.Print m.Value  '输出匹配结果的值
      Debug.Print m.SubMatches(0)  '输出保存的第1个分组
    Next
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
abCD123CD
CD

可见,由于正则表达式中第1个分组为非捕获分组,它不参与编号,所以输出保存的第1个分组结果为CD,而非ab。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="abCD123CDbc"
>>> m=re.finditer(r"(?:ab)(CD)\d+\1",a)
>>> for i in m:
		print(i.group())
abCD123CD

用re.finditer函数获取匹配迭代器,用for循环获取匹配结果。结果显示,匹配字符串中是包括"ab"的。

下面用re.search函数进行查找,返回Match对象m,调用该对象的groups属性查看各分组的子字符串。

code.python
>>> m=re.search(r"(?:ab)(CD)\d+\1",a)
>>> m.groups()
('CD',)

仅返回1个分组结果"CD"。此结果说明第1个分组因为声明为非捕获分组,不参与编号,也不保存。

零宽断言

零宽断言用于查找指定内容之前或之后的内容,不包括指定内容。有两种类型,即

零宽度正预测先行断言:表达式为(?=exp),查找exp表示的内容之前的内容。

零宽度正回顾后发断言:表达式为(?<=exp),查找exp表示的内容之后的内容。

组合上面两种情况,可以查找指定内容之间的内容。

下面给定原始字符串,要求提取出单位公斤前面的数字,只提取数字。使用零宽度正预测先行断言进行提取。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\零宽断言.xlsm。

code.vba
Sub Sam28()
  '省略部分代码
  '…
  strT = "10公斤 20公斤 30公斤"  '给定的字符串
  With objReg
    .Global = True
    .Pattern = "\d+(?=公斤)"  '正则表达式
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '变量查找结果
      Debug.Print matT  '输出结果
    Next
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
10
20
30

正则表达式r"\d+(?=公斤)"表示匹配"公斤"前面的数字,不包括"公斤"。结果显示匹配正确。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="10公斤 20公斤 30公斤"
>>> m=re.finditer(r"\d+(?=公斤)&quot;,a)  #只取单位之前的数字
>>> for i in m:
		print(i.group())
10
20
30

下面给定原始字符串,要求提取出"同学"、"战友"、"师兄"等称谓后面的姓名。使用零宽度正回顾后发断言进行提取。

code.python
>>> import re
>>> a="同学李海 战友王刚 师兄张三"
>>> m=re.finditer(r"(?<=同学|战友|师兄)\w+",a)  #只取称呼后面的姓名
>>> for i in m:
		print(i.group())
李海
王刚
张三

正则表达式r"(?<=同学|战友|师兄)\w+"表示匹配"同学"、"战友"或"师兄"等称谓后面的子字符串。各称谓使用分支条件进行匹配。匹配的结果不包括称谓。

使用Excel VBA进行此项操作时失败,无法完成。

负向零宽断言

负向零宽断言用于断言指定位置的前面或后面不能匹配指定的表达式。有两种类型,即

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

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

下面给定原始字符串,要求匹配数字123前面是字母、数字或下划线,后面不能跟大写字母。使用零宽度负预测先行断言进行匹配。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\负向零宽断言.xlsm。

code.vba
Sub Sam31()
  '匹配数字123前面是字母、数字或下划线,后面不能跟大写字母
  '省略部分代码
  '…
  strT = "5123Wgh123hp123456"  '给定的字符串
  With objReg
    .Global = True
    .Pattern = "\w123(?![A-Z])"  '正则表达式
    Set mcT = .Execute(strT)  '执行查找
    For Each matT In mcT  '遍历结果
      Debug.Print matT  '输出结果
    Next
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
h123
p123

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a="5123Wgh123hp123456"
>>> m=re.finditer("\w123(?![A-Z])",a)
>>> for i in m:
		print(i.group())
h123
p123

可见,给定字符串中第1个"123"因为后面跟了大写字母W,不能匹配。

贪婪与懒惰

前面介绍*和+时,是匹配尽可能多的字符,称为贪婪匹配。但有时候需要匹配尽可能少的字符,称为懒惰匹配,方法是在贪婪匹配的后面添加一个问号。

常见的懒惰匹配格式如表8-6中所示。

表8-6 懒惰匹配

懒惰匹配格式 说 明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

下面给定原始字符串,分别使用贪婪匹配和懒惰匹配比较匹配结果。

【Excel VBA】

示例文件的存放路径为Samples\ch20\Excel VBA\贪婪与懒惰.xlsm。

code.vba
Sub Sam32()
  '省略部分代码
  '…
  strT = " 123  abc53  59wt ""  '给定的字符串
  With objReg
    .Global = True
    .Pattern = "\s.+?\s""  '正则表达式
    Set mcT = .Execute(strT) "  '执行查找
    For Each matT In mcT"  '遍历查找结果
      Debug.Print matT"  '输出结果
    Next
  End With
End Sub

运行过程,在立即窗口中输出结果:

code.vba
123
abc53
59wt

正则表达式"\s.+?\s"中+后面有?,此为懒惰匹配,在两个空白符之间匹配尽可能少的字符,所以匹配结果是空格间隔的三个子字符串。

【Python】

在Python Shell窗口输入:

code.python
>>> import re
>>> a=" 123  abc53  59wt "
>>> m=re.finditer("\s.+\s",a)
>>> for i in m:
		print(i.group())
123  abc53  59wt

正则表达式"\s.+\s"中没有?,此为贪婪匹配,在两个空白符之间匹配尽可能多的字符,所以匹配结果是整个字符串。

code.python
>>> m=re.finditer("\s.+?\s",a)
>>> for i in m:
		print(i.group())
123
abc53
59wt

正则表达式"\s.+?\s"中+后面有?,此为懒惰匹配,在两个空白符之间匹配尽可能少的字符,所以匹配结果是空格间隔的三个子字符串。[大谦Excel,dqexcel点com]