正则表达式


正则表达式

正则表达式(英语:Regular expression,常简写为regex、regexp或RE)就是字符匹配的工具;是由正则符号和普通字符组成,来匹配不同规律的字符串。
一个正则表达式通常被称为一个模式(pattern),为用来描述或者匹配一系列匹配某个句法规则的字符串。

普通字符&元字符

普通字符在正则表达式中,代表字符本身。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。

完整字符匹配
eg: ‘abc’ –> 匹配一个字符串,第一个字符是’a’,第二个字符是’b’,第三个字符也是最后一个字符是’c’

元字符就是指那些在正则表达式中具有特殊意义的专用字符,’\大写字母’对应的功能是’\小写字母’功能取反

常用字符

常用字符 说明 例子
. 匹配除换行符以外的任意字符 ‘a.c’ –> 匹配一个长度上3的字符串,并且第一个字符是’a’,最后一个字符是’c’,中间是任意字符
\w 匹配字母或数字或下划线或汉字 ‘\w\w…’ –> 匹配一个长度是5的字符串,并且字符串的前两位是数字、字母和下划线,后面是三个任意字符
\s 匹配任意的空白符 包括:空格、制表符和换行符(空格,\t,\r,\n) ‘a.c’ –> ‘\w\w\s\w’ –> 匹配一个长度是4的字符串,并且字符串前两位是字母数字或者下划线(中文),中间一个空白,再一个字母数字或者下划线(中文)
\d 匹配数字字符 ‘\d\d\d..’ –> 匹配一个长度是5的字符串,字符串的前三位是数字字符,后两位是任意字符
\W 匹配非数字或字母或下划线 ‘\d\D\s\w\Ba’ –> 匹配一个字符串,第一个字符是数字、第二个是非数字,第三个是空白,第四个是数字字母下划线,最后一个是a,并且要求a前面不是单词边界
\D 匹配非数字字符 同上
\S 匹配非空白字符 同上
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符 ‘.‘ –> 匹配’.’这个字符
( ) 标记一个子表达式的开始和结束位置
[ 标记一个中括号表达式的开始
{ 标记限定符表达式的开始
| 指明两项之间的一个选择

定位符

定位字符 说明 例子
^ 匹配字符串的开始 ‘^The..’ –> 匹配一个字符串前面三个字符是’The’,后面两个任意字符
$ 匹配字符串的结束 ‘The$’ –> 匹配一个字符串前面三个字符是’The’,并且e后面是字符串结尾
\b 匹配单词边界 检测\b出现的位置是否是单词边界,不会对字符进行匹配。单词边界:字符串开头、字符串结尾、标点符号、空白符号等 ‘\bhello,\bworld’ –> 匹配字符串’hello,world’, 并且要求w前面是单词边界, h前面也是单词边界
\B 匹配非单词边界 ‘\d\D\s\w\Ba’ –> 匹配一个字符串,第一个字符是数字、第二个是非数字,第三个是空白,第四个是数字字母下划线,最后一个是a,并且要求a前面不是单词边界
注意:不能将限定符与定位符一起使用。由于在紧靠换行或者单词边界的前面或后面不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。

限定符

限定字符 说明 例子
* 匹配前面的子表达式零次或多次 ‘a\d*’ –> 匹配一个字符串前面第一个字符是a,后面数字可有可无
+ 匹配前面的子表达式一次或多次 ‘a\d+’ –> 匹配一个字符串前面第一个字符是a,后面数字有
? 匹配前面的子表达式零次或一次 ‘a\d+’ –> 匹配一个字符串前面第一个字符是a,后面数字无或者只有1个
{n} 匹配确定的n次(n非负整数) ‘a{3}’ –> 匹配’aaa’
{n,} 至少匹配n次(n非负整数) ‘a{1,}’ –> 匹配’a’、’aa’、…
{,m} 至多匹配m次(m非负整数) ‘a{,3}’ –> 匹配’’、’a’、’aa’、’aaa’
{n,m} 最少匹配n次且最多匹配m次(m和n均为非负整数,其中n<=m) ‘a{1,3}’ –> 匹配’a’、’aa’、’aaa’

非打印字符

非打印字符 说明 例子
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符

字符集

匹配中括号出现的任意一个字符
一个中括号只能匹配一个字符
正则中有特殊功能的单个符号在[]都表示符号本身 例如:. $ ^ + * ? |等
匹配字符的组合符号,在中括号中保持原来的功能, 例如: \w \d \s \W \D \S

[普通字符集] - 匹配中括号出现的任意一个字符

eg: ‘[abc]’ –> 匹配一个字符是a或者b或者c;
‘\d[bc=\d]’ –> 匹配一个长度是2的字符串,第一个字符是数字,第二个字符是b或者c或者=或者数字

[字符1-字符2] - 表示字符1到字符2(注意:要求字符1的编码值要小于字符2)

[a-z] - 表示匹配所有小写字母
[A-Z] –> 表示匹配所有大写字母
[a-zA-Z] –> 匹配所有的字母
[1-7] –> 匹配数字字符1到7
[\u4e00-\u9fa5] –> 匹配所有的中文
eg: ‘[1-7][abc-][a-z]’ –> 匹配一个长度是3的字符串,第一个字符是数字字符1到7中的一个,第二个字符是’a’,’b’,’c’,’-‘中的一个,第三个字符是小写字母

[^字符集] - 匹配不在字符集中的任意一个字符

[^abc] - 匹配除了’a’,’b’,’c’以外的其他任意一个字符
[^\d] –> 匹配除了数字字符以外的其他任意一个字符
[^a-z] –> 匹配除了小写字母以外的其他任意一个字符
[abc^] –> 匹配’a’,’b’,’c’或者’^’中的任意一个字符

分枝条件

正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开
条件1 | 条件2 - 匹配条件1或者条件2
注意:正则中的分之也会出现短路,当条件1可以匹配,就不会再使用条件2进行匹配
‘\d{2}|[a-z]’ –> 匹配两个数字字符或者一个小写字母
‘a\d{2}|\w{2}’ –> 匹配一个a后面两个数字,或者两个数字字母下划线

分组

  • 分组 - 将括号中的内容作为一个整体
    ‘(\d[a-z]){3}’ –> 匹配一个字符串,以’数字小写字母’的形式出现3次
    ‘abc(\d{3}|[A-Z]{3})’ –> 匹配一个字符串,前三位是’abc’,后三位是三个数字或者三个大写字母

后向引用

用于重复搜索前面某个分组匹配的文本

  • 常用分组语法
分类 代码/语法 说明
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
捕获 (?exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)
捕获 (?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配exp前面的位置
零宽断言 (?<=exp) 匹配exp后面的位置
零宽断言 (?!exp) 匹配后面跟的不是exp的位置
零宽断言 (?<!exp) 匹配前面不是exp的位置
注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读
  • 捕获 - 通过正则获取符合条件的子串的时候,可以在正则表达式中加括号,匹配后只获取括号里面匹配到的内容
    ‘a(\d+)’ –> ‘ahsa783+sdh*92dfjhjj78jhsda67jk’ –> 匹配两段:’783’、’67’
  • 重复匹配 - 带多个分组的正则表达式中可以分组的后面通过添加’\数字’来重复前面第几个分组中匹配到的内容

    \数字重复之前的内容 必须一样
    \数字 - 这儿的数字代表前面第几个分组; \1代表第一个分组 \2代表第二个分组

‘(\d{3})([a-z]{2})a\1{2}-\2’ –> ‘123efa123123-ef’ –> 完全匹配,1-3位数字,4-5位小写字母,6位a,7-12位数字(重复分组1*2),13位-,14-15位小写字母(重复分组2)

  • 零宽断言 - 查找在某些内容(但并不包括这些内容)之前或之后的东西
    • (?=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’
  • 负向零宽断言 - 查找不是某个字符或不在某个字符类里的字符的方法
    • (?!exp)
      零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp
      ‘\d{3}(?!\d)’ –> 匹配三位数字,而且这三位数字的后面不能是数字 –> ‘11ddf118ggg’ –> ‘ggg’
      ‘\b(?!abc)\w+\b’ –> 匹配不包含连续字符串abc的单词 –> ‘abc118 1 a2’ –> ‘1’、’a2’
    • (?<!exp)
      零宽度负回顾后发断言,断言此位置的前面不能匹配表达式exp
      ‘(?<![a-z])\d{7}’ –> 匹配前面不是小写字母的七位数字 –> ‘abc1234567A7654321’ –> ‘7654321’
  • 注释
    ‘2[0-4]\d(?#200-249)’ –> 匹配200-249 –> ‘abc1234567A7654321’ –> ‘234’

贪婪与懒惰

  • 贪婪匹配 - 匹配尽可能多的字符
    ‘a.+b’ –> ‘xxxahdjbnnkbsssammmb’ –> ‘ahdjbnnkbsssammmb’
  • 懒惰匹配 - 匹配尽可能少的字符
    ‘a.+?b’ –> ‘xxxahdjbnnkbsssammmb’ –> ‘ahdjb’、’ammmb’
  • 懒惰限定符
    代码/语法 说明
    *? 重复任意次,但尽可能少重复
    +? 重复1次或更多次,但尽可能少重复
    ?? 重复0次或1次,但尽可能少重复
    {n,m}? 重复n到m次,但尽可能少重复
    {n,}? 重复n次以上,但尽可能少重复

Python中的使用

import re


# re.compile(pattern, flags=0) 将正则表达式的样式编译为一个正则表达式对象(正则对象)
# 如果需要多次使用这个正则表达式的话,使用 re.compile() 和保存这个正则对象以便复用,可以让程序更加高效
re_str = r"\d{3}"
re_obj = re.compile(re_str)
re.fullmatch(re_str, "234") # 调用模块中的函数 <re.Match object; span=(0, 3), match='234'>
re_obj.fullmatch("234") # 调用对象方法 <re.Match object; span=(0, 3), match='234'>

# re.fullmatch(pattern, string, flags=0) 如果整个string匹配到正则表达式样式,就返回一个相应的匹配对象
re.fullmatch(r"\d{5,}", "ddf123456") # None
re.fullmatch(r"\d{5,}", "123456") # <re.Match object; span=(0, 6), match='123456'>

# re.match(pattern, string, flags=0) 如果string开始的0或者多个字符匹配到了正则表达式样式,就返回一个相应的匹配对象
re.match(r"\d{5,}", "ddf123456") # None
re.match(r"\d{5,}", "123456ddf") # <re.Match object; span=(0, 6), match='123456'>
re.match(r"\d{5,}?", "123456ddf") # <re.Match object; span=(0, 5), match='12345'>

# re.search(pattern, string, flags=0) 扫描整个字符串找到匹配样式的第一个位置,并返回一个相应的匹配对象
re.search(r"\d{5,}", "ddf123456") # <re.Match object; span=(3, 9), match='123456'>

# re.split(pattern, string, maxsplit=0, flags=0)
# 用pattern分开string。如果在pattern中捕获到括号,那么所有的组里的文字也会包含在列表里。如果maxsplit非零,最多进行 maxsplit 次分隔,剩下的字符全部返回到列表的最后一个元素。
re.split(r"[a-f]+", "0a3B9a008", flags=re.IGNORECASE) # ['0', '3', '9', '008']
re.split(r"[a-f]+", "0a3B9a008") # ['0', '3B9', '008']
re.split(r"[a-f]+", "0a3B9a008", 1, flags=re.IGNORECASE) # ['0', '3B9a008']
re.split(r"([a-f]+)", "0a3B9a008", flags=re.IGNORECASE) # ['0', 'a', '3', 'B', '9', 'a', '008']
re.split(r"([a-f]+)", "0a3B9a008") # ['0', 'a', '3B9', 'a', '008']

# re.findall(pattern, string, flags=0)
# 在字符串中获取满足正则表达式的所有的字符,返回一个列表,列表元素是字符串
# 如果这个正则表达式中有一个分组,结果是列表中只那个分组匹配到的结果
# 如果这个正则表达式中分组的个数大于1,结果是一个列表,列表中的元素是元祖,元祖中是每个分组匹配到的内容
re.findall(r"[a-zA-Z]{2,}\d+[a-z]+?", "haja37jjkd89sdhs909nnna238") # ['haja37j', 'jkd89s', 'dhs909n']
re.findall(r"[a-zA-Z]{2,}(\d+)[a-z]+?", "haja37jjkd89sdhs909nnna238") # ['37', '89', '909']
re.findall(r"[a-zA-Z]{2,}(\d+)([a-z]+?)", "haja37jjkd89sdhs909nnna238") # [('37', 'j'), ('89', 's'), ('909', 'n')]

# re.finditer(pattern, string, flags=0)
# 获取字符串中满足正则表达式的内容,返回的是一个迭代器,迭代器中的元素是匹配对象
res = re.finditer(r"[a-zA-Z]{2,}(\d+)([a-z]+?)", "haja37jjkd89sdhs909nnna238===") # <callable_iterator object at 0x00000162676BB340>
next(res) # <re.Match object; span=(0, 7), match='haja37j'>

# re.sub(pattern, repl, string, count=0, flags=0)
# 用新子串替换字符串中满足正则表达式的子串,返回一个替换后的字符串
# repl可以是字符串或函数;如为字符串,则其中任何反斜杠转义序列都会被处理;如为函数,那它会对每个非重复的pattern的情况调用
re.sub(r"\sAND\s", " & ", "Baked Beans And Spam", flags=re.IGNORECASE)

def foo(matchobj):
    if int(matchobj.group(0)) & 1: return "odd"
    else: return "even"

re.sub(r"\d+", foo, "who i am,1,2,123") # 'who i am,odd,even,odd'
re.sub(r"\d+", foo, "who i am,1,2,123", count=1) # 'who i am,odd,2,123'

# re.subn(pattern, repl, string, count=0, flags=0)
# 返回一个元组(字符串, 替换次数).
re.subn(r"\d+", foo, "who i am,1,2,123") # ('who i am,odd,even,odd', 3)

# 匹配对象
# 匹配对象总是有一个布尔值True。如果没有匹配的话match()和search()返回None所以可以简单的用if语句来判断是否匹配
# Match.start([group]) 获取匹配结果的开始下标
# Match.end([group]) 获取匹配结果的结束下标
# 匹配对象.start()/匹配对象.end() - 获取整个正则表达式匹配到的开始下标/结束下标
# 匹配对象.start(n)/匹配对象.end(n) - 获取正则表达式中第n个分组匹配到的开始/结束下标
m = re.match(r"\d{5,}?", "123456ddf")
m.start() # 0 
m.end() # 5

# Match.span([group])
# 匹配到的内容的范围,(开始下标, 结束下标)
# 匹配对象.span() - 获取整个正则表达式匹配到的范围
# 匹配对象.span(n) - 获取正则表达式中第n个分组匹配到的范围(前提是有分组)
m.span() # (0, 5)

# Match.group([group1, ...]) 获取匹配到的内容
# 匹配对象.group() - 获取整个正则表达式匹配到的内容
# 匹配对象.group(n) - 获取正则表达式中第n个分组匹配到的内容
m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
m.group(0) # 'Isaac Newton'
m.group(1) # 'Isaac'
m.group(1, 2) # ('Isaac', 'Newton')

Golang中的使用

package main

import (
	"fmt"
	"regexp"
	"strings"
)

func main() {
	// regexp.Compile() -> (*Regexp, error) 编译正则表达式
	re, _ := regexp.Compile("#(?U)(.+)#") // (?U)表示无视大小写模式
	template := "Hi 您有一个提醒通知,请输入 性质:全职,状态:正式,#retire_date#,未填写#ov_learning_style#15天,男,离异否是否#residential_address#"
	// Regexp.FindAllString() 所有字符串查询 返回表达式的所有连续匹配的切片
	findTemplate := re.FindAllString(template, -1)
	fmt.Println(findTemplate) // [#retire_date# #ov_learning_style# #residential_address#]
	// Regexp.FindAllStringSubmatch() 所有字符串查询 返回表达式的所有连续匹配的切片 包含分组内的值
	fmt.Println(re.FindAllStringSubmatch(template, -1)) // [[#retire_date# retire_date] [#ov_learning_style# ov_learning_style] [#residenti al_address# residential_address]]
	// Regexp.ReplaceAllString() 所有字符串替换
	reTemplate := re.ReplaceAllString(template, "")
	fmt.Println(reTemplate) // Hi 您有一个提醒通知,请输入 性质:全职,状态:正式,,未填写15天,男,离异否是否
	// Regexp.ReplaceAllStringFunc 所有字符串替换 函数
	re = regexp.MustCompile(`[^aeiou]`)
	fmt.Println(re.ReplaceAllStringFunc("seafood fool", strings.ToUpper)) // SeaFooD FooL
	// regexp.MustCompile() -> *Regexp 编译正则表达式
	fileUrl := "https://pub-cdn-dev.xxxx.com/f97c46278b05449eba6ecb9068508f1b?attname=ceshi.xlsx&attname=xixi.xlsx"
	re = regexp.MustCompile("attname=(.*?).xlsx")
	// Regexp.FindStringSubmatch() 字符串查询,其中包含查询字符串中正则表达式的最左侧匹配文本及其子表达式的匹配项
	matchArr := re.FindStringSubmatch(fileUrl)
	fmt.Println(matchArr) // [attname=ceshi.xlsx ceshi]
}

参考文档

deerchao博客
Python re 文档
Golang regexp 文档


文章作者: ddf_samsara
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ddf_samsara !
  目录