ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

正则表达式系列 (二)

2021-10-10 15:33:52  阅读:184  来源: 互联网

标签:regex search 匹配 正则表达式 环视 括号 2022 系列


前文链接:正则表达式系列 一

本文介绍的是正则的各种性质,有阅读难度,建议先阅读前文

括号

这里讲的括号特指小括号。

分组作用

最直白的含义,即将一部分元素括起来视为一个整体。

#python
re.search(r"^ab+$","abab") != None	#False,即无法匹配
re.search(r"^(ab)+$","abab") != None	#True,可以匹配

ab+的含义是匹配一个字符a开头,后面跟1个或多个字符b的字符串。所以不能匹配(注意用^和$限制匹配范围)

(ab)+的含义是匹配一个或多个字符串“ab”,所以能匹配

多选分支

这个能力需要借助元字符“|”,表示“或”的含义

#python
regex = r"^([a-zA-Z]+|[0-9]+)$"
re.search(regex, "19987234") != None	#True
re.search(regex, "cajsdioc") != None	#True
re.search(regex, "89dsa") != None	#False

regex中含有两个字符组,[a-zA-Z]+匹配一个纯字母的字符串,[0-9]+匹配一个纯数字的字符串。二者用或相连。所以既能匹配纯数字串,也能匹配纯字母串,但无法匹配混杂字母和数字的字符串

多选分支还有一些注意事项

  • 多选分支也可以没有括号。因为元字符“|”的运算优先级非常低。ab|cd和(ab|cd)的效果完全相同

  • 能用字符组实现的功能,不要用多选分支,因为相同情况下多选分支效率差得多

  • 多选分支的排列顺序,大部分语言是优先匹配靠左的分支条件。所以如果写出的两个分支能匹配同一个字符串,最后匹配结果是靠左的分支。

    #python
    re.search(r"(ljj|ljjliujunjie)", "ljjliujunjie123").group()
    #输出 ljj
    re.search(r"(ljjliujunjie|ljj)", "ljjliujunjie123").group()
    #输出 ljjliujunjie
    

    所以如果没有必要,不要写出会重复匹配的分支。但如果必须要重复,那就把最难触发的分支靠前写。

引用分组

举个例子先,如果你希望从2022-10-11这种日期中截取年月日,那很自然的写出正则regex1 = r"\d{4}-\d{2}-\d{2}",但这个虽然能匹配,但无法截取年月日。所以正确的做法是下面这种

#python
regex2 = r"(\d{4})-(\d{2})-(\d{2})"
res = re.search(regex2, "2022-10-11")
res.group(0)	#2022-10-11
res.group(1)	#2022
res.group(2)	#10
res.group(3)	#11

默认情况下,括号中的匹配内容会被保存下来,从1开始编号形成一个列表返回。至于为啥从1开始编号,是因为如果没有加任何括号,整体的返回结果会被默认为0。如果括号存在嵌套,分组的编号是以开括号出现的顺序来计数

还有一个需要注意的特点,所谓引用分组,引用的是对应括号的表达式最终匹配到的文本,或者叫捕获到的文本。比如下面这个错误

#python
res.search(r"(\d){4}-(\d{2})-(\d{2})", "2022-10-11").group(1)
#返回 2 

这里把第一个括号的限定范围缩小了,(\d){4} 这个整体的表示意思仍然是匹配一个长度为4的连续数字串,但括号内的表达式是 \d,它的意思是匹配一个长度为1的数字串。所以匹配时,先匹配到2022的第一个字符2,此时1号括号的捕获结果是2,再匹配到2022的第二个字符0,此时1号括号的捕获结果就被更新为0。依次进行,最终1号括号的捕获结果是2022的最后一个字符2。

引用分组不仅可以用于获取特定匹配结果,还可以替换指定内容,只需要更改下调用的正则api即可,建议查询对应语言文档

引用分组还有些特殊用法

反向引用

如果想判断一个单词是否存在连续出现的重复字,比如pool,book,hello等等,我们不能用[a-z][a-z]这种方式来匹配,因为两个字符组是各自匹配互不干扰的,不能保证匹配结果一定相同。

反向引用则提供一种能力,允许正则表达式的内部成员,获取到靠前部分的表达式的匹配结果。

所以只需要用([a-z])\1,后面的\1表示引用编号为1的括号的匹配内容。同理,如果多个括号,就依次\2,\3等等

不过切记,引用的是文本而不是表达式,不要试图用反向引用实现“重复某个正则表达式”。比如

#python
#下面两个正则表达式都试图匹配 3.1.2.0 这种字符串
regex1 = r"([0-9]\.){3}[0-9]"	#正确
regex2 = r"([0-9])\.\1\.\1\.\1"	#错误

第二个之所以错误,是因为\1引用的是([0-9])的匹配结果,比如匹配3.1.2.0时,第一个小括号的匹配结果是3,那么regex2此时就等价于"3\.3\.3\.3",所以只能匹配3.3.3.3。

命名分组

引用分组的引用默认是数字编号,如果括号较多便很不直观,所以可以用下面语法给括号命名

#python
regex1 = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
regex2 = r"(\d{4})-(\d{2})-(\d{2})"

re.search(regex1, "2022-10-11").group("year")	#2022
re.search(regex2, "2022-10-11").group(1)	#2022
#即便命名了,也可以用数字编号
re.search(regex1, "2022-10-11").group(1)	#2022

上述两种正则表达式是完全等价的。另外注意,上述代码的语法是python规定的,其余语言不尽相同

非捕获分组

括号的引用能力是默认开启的,虽然很方便,但是性能很低。如果你仅仅需要一个括号的分组能力,可以使用如下语法关闭这个括号的引用能力,这种语法叫作非捕获分组。引用编号时会跳过非捕获分组

#python
re.search(r"(?:\d{4})-(\d{2})-{\d{2}}", "2022-10-11").group(1)
#10

虽然这个语法看起来怪,但其实大部分情况我们不需要引用能力,只是简单分组,所以尽量使用非捕获分组来替代默认

断言

正则表达式中有些结构,虽然也能匹配到内容,但这种匹配只是用于分支判断,匹配的内容也不会出现在结果中,这样的结构被称为断言。

单词边界

假设需要下面这段文本中的单词“ljj”,很自然会写出正则 regex = r"ljj"。但很不幸会发现有两处匹配,"ljjliujunjie123@some.com"的前缀ljj也被视为了一个单词

text = "ljj has a email which is ljjliujunjie123@some.com"

说明单词ljj 并不等于 字符串ljj。所以正则提供了特殊元字符 \b,表示当前位置,一侧是单词字符,一侧不是单词字符。这样的位置自然就是一个单词的边界。所以改写一下,regex = r"\bljj\b"就能正确匹配了

有一点需要注意,单词边界\b要求中,“一侧不是单词字符”,含义是既可以出现非单词字符,也可以没有任何字符

行起始or结束位置

类似单词边界的匹配,我们匹配位置时,匹配到的是两个字符之间的地方,这种匹配又被叫作锚点

正则中关于行的起始与结束提供了如下锚点元字符

^ 匹配整个字符串/文本的起始位置,和行终止符之后的位置
$ 匹配行终止符之前的位置(但在JS语言中,$匹配的是行终止符之后的那个位置)
\A 不论在任何模式下,永远匹配整个字符串/文本的起始位置
\z 不论在任何模式下,永远匹配整个字符串/文本的结束位置(即行终止符之后的位置)

其中前两个是所有语言都支持的,也是最常用的,后两个在不同语言下语言不尽相同,使用前查文档

环视

顾名思义:站在某个位置,环顾四周的情况。正则的环视就是站在当前位置,考察其前面的字符串或后面的字符串是否满足特定要求。其语法分为如下四种

名字语法方向含义
肯定顺序环视(?=regex)向右匹配匹配到当前位置时,其右侧的子表达式必须满足regex
否定顺序环视(?!regex)向右匹配匹配到当前位置时,其右侧的子表达式必须不能满足regex
肯定逆序环视(?<=regex)向左匹配匹配到当前位置时,其左侧的子表达式必须满足regex
否定逆序环视(?<~!regex)向左匹配匹配到当前位置时,其左侧的子表达式必须不能满足regex

举个例子,如果我想从下面的字符串中匹配这样的子字符串,其左侧必须是#,其右侧不能是?,其本身必须是由小写字母构成的字符串。

#python
text = "ljj want #to be? a# ?#better? coder"
regex = r"(?<=#)[a-z]+(?!\?)"
re.search(regex, text)
#匹配结果是 to 和 bette

环视的注意事项:

  • 环视并不真正匹配字符,它只是在新增一些限制,所以适合用于修改复杂的正则表达式
  • 环视语法本身用到了括号,但这个括号是不会被记入引用括号的编号。但如果环视的子表达式中出现了捕获型括号,该捕获型括号会被算入引用编号,但同时注意,由于环视的特殊性,一旦从环视结构中跳出,其中所有的捕获型括号的匹配内容都会被清空,所以外部无法获取环视内部捕获型括号的匹配内容
  • 顺序环视在大部分语言中都支持,逆序环视的支持程度差很多,使用需谨慎
  • 环视可以进行嵌套,因为环视结构中的子正则表达式是一个完整的正则,理论上可以随便写
  • 环视可以并列,结果取它们的交集。比如(?=[0-9]+)(?!666)表示当前位置右侧需要是数字串,但不能是666

模式

正则的常用匹配模式有以下四种,不同模式会影响特定元字符的含义

选择模式有两种方法,一是模式修饰符,直接写在正则表达式中,二是预定义常量,是不同语言提供的一些参数,传入这些参数即可自动构建包含对应模式修饰符的正则表达式。

模式修饰符的语法是(?modifier),写在一个正则的最开头

无大小写模式

modifier = i,不区分字母的大小写

#python
re.search(r"(?i)hello", "HELLO") != None	#True
单行模式

modifier = s,表示将文本中的换行符视为普通字符,可以用元字符点号.来匹配(默认情况.不能匹配换行符)

单行模式在部分语言中叫作点号通配,这个叫法听起来更好,因为单行模式和下面的多行模式其实毫无关系

另外,JavaScript不支持单行模式

多行模式

modifier = m,默认模式下,^和$的匹配方式如这里所示,但在多行模式下,它们可以匹配一个文本内部的某一行字符串的开始和结束位置

注释模式

modifier = x,这是为了复杂正则设计的,例如

#python
regex = r"""
(?x)	#启用了注释模式
[0-9]	#匹配数字
[a-z]	#匹配字母
"""

关于模式有一些补充

  • 真实使用时,更推荐用对应语言的预定义常量,可以通过这个网站查询https://www.runoob.com/。原因是不同语言对这些模式的支持程度不一样,甚至含义也不太一样
  • 模式是可以同时使用多个的,比如(?ismx)就表示同时启用这四种模式,它们事实上是不冲突的
  • 模式修饰符的范围可以自定义的,如果写在开头,就是对整个正则生效,如果写在中间,就是对它后面的子正则生效。如果用括号括起来,可以限定指对某个子串生效,如"h((?i)e)llo"表示只对e这个字符不区分大小写

标签:regex,search,匹配,正则表达式,环视,括号,2022,系列
来源: https://blog.csdn.net/ljjliujunjie123/article/details/120686931

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有