刘刚刚的个人博客

正则表达式--分好类,会更好记

创建时间:2020-09-26 20:01:33
更新时间:2020-09-27 00:01:03

各个语言通用,一次学好,一直有用。

正则表达式可以用来匹配字符串中的字符。比如:在各语言中都有相应的支持。

正则表达式的分类

  • 基础正则表达式(BRE):元字符需要转义
  • 扩展正则表达式(ERE):元字符不需要转义即可使用
  • 编程语言支持的高级正则表达式:支持更多的特性

不同语言的应用

  1. shell-grep
  2. python

    import re
    
    s = 'hello world! 123 he'
    
    # 编译正则表达式
    rule = rule = re.compile(r'he')
    
    # 使用match匹配,match只会从头开始匹配
    res = rule.match(s) # 如果匹配失败,则返回None,成功返回对象
    res.span() #输出匹配到的字符区间
    res.group() #输出匹配到的内容,如果正则表达式中有多个分组,可以获取到分组的结果
    
    # 使用search,在字符串中查找符合表达式的结果
    rule = rule = re.compile(r'world')
    res = rule.search(s)  # 在字符串中查找,如果成功返回一个匹配的对象,否则返回None
    
    # 使用sub,替换字符串中匹配到的内容
    rule = re.compile(r'world')
    res = rulr.sub('T', s, count=0) # 返回替换后的字符串
    
    # findall,查找所有的字符串,返回列表
    rule = re.compile(r'h')
    res = rule.findall(s)

基础语法

匹配字符

  • abc 匹配字符串‘abc',普通字符的匹配
  • [abc] 匹配中括号中的任意一个字符
  • n 匹配换行符
  • t 匹配制表符
  • w 匹配单词字符 [a-zA-Z0-9_]
  • W 非单词字符
  • s 空白字符
  • S 非空白字符
  • d 匹配数字
  • D 匹配非数字

中括号表达式

  • 普通中括号包围的字符组:[abcde]表示某单个字符匹配中括号内的任一字符即匹配成功
  • 取反表示法:[^abc]中括号内开头使用 ^ ,表示只要不是中括号中的字符就匹配
  • 范围表示法:

    [a-z] :代表任一单个小写字母

    [^a-z] :只要单个非小写字母的其它任一单个字符

    [A-Z] :代表任一单个大写字母

    [0-9] :代表任一单个数字

    注: [0-59] 表示匹配 0、1、2、3、4、5、9 而不是0到59中间的数值

    [a-z0-9A-Z] :代表任一字母或数字

    [a-z0-9A-Z_] :代表任一字母、数字或下划线,即匹配单词字符(word)

    [A-z][a-Z] :建议不要使用这种横跨大小写字母的范围表达式,不同地方表达的含义不同

    有些正则工具按照字典顺序排序时, [a-d] 不是等价于abcd,而是等价于aBbCcDd。如果想要等价于abcd,应将locale环境设置为C: LC_ALL=C
  • 特殊元字符在中括号中的匹配:

    1. 中括号中匹配 ^ ,需将其放在中括号的非开头位置,如 [a^]
    2. 中括号中匹配 - ,需将其放在开头位置或结尾位置,如 [abc-] 、 [-abc]
    3. 中括号中匹配 ] ,需将其放在开头位置,如 []abc]

如果需要同时匹配上面2个或三个元字符, []^] 、 [-^] 、 []-] 、 []^-]

字符类

image-20200926151916535

有些语言还额外提供一些非POSIX的字符类,比如 [:ascii:] 表示任一ASCII表中的字符。

使用字符类时,需使用中括号包围:

  • [[:alpha:]] :任一字母
  • [^[:alpha:]] :任一非字母
  • [^[:alpha:]0-3] :任一非字母且非0 1 2 3

位置锚定

字符匹配会消耗字符。位置匹配,只是匹配位置,不会消耗字符。

只匹配位置,不匹配字符,所以不会消耗字符数量,也称为即零宽断言

  • ^ 匹配行首
  • $ 匹配行尾
  • < :匹配单词开头处的位置
  • b :匹配单词边界处的位置(开头和结尾),所以 bwordb 等价于 <word>
  • B :匹配非单词边界处的位置

量词

量词它是正则表达式中的一个隐含的修饰符,它修饰它前面一个字符或前面一个子表达式,它自身不是正则表达式中的独立元素,量词和它所修饰的字符或子表达式组合起来才是正则表达式中的独立元素。

  • {m} 表示匹配前一个字符或前一个子表达式m次

    "a{3}":匹配a3次。aaa aab aaaa

    "[abc]{3}": aaa bbb ccc aba abc cba

  • {m,n} (m<n)表示匹配前一个字符或前一个子表达式最少m次,最多n次

"a{3,5}" aaa aaaa aaaaa

  • {m,} 表示匹配前一个字符或前一个子表达式至少m次

"3{2,}" 33 333 33333333 333333333333333333333

  • {,n} 表示匹配前一个字符或前一个子表达式至多n次(注:不一定会支持该语法)"a{,3}" a aa aaa aaaa

注意:匹配0次也算匹配成功,只不过这时候匹配的是空字符,grep没法显示出来

  • ? 表示匹配前一个字符或前一个子表达式0或1次,等价于 {0,1} 等价于 {,1}

"ab?c" "abc" "ac"

  • * 表示前一个字符或前一个子表达式匹配0或多次,即任意次数 等价于 {0,}

a3*b :可以匹配ab、a3b、a33b、a33333333b

  • .* :匹配任意长度的任意字符

问:为什么 .* 匹配的是任意字符任意长度,而不是某个字符任意长度?

.* 3 333 33333 abcdef

  • + 表示匹配前一个字符或前一个子表达式1或多次,即至少一次 等价于 {1,}

这些量词均为贪婪匹配模式,即尽可能多的去匹配符合条件的字符。

基础正则和扩展正则都只支持贪婪匹配,不支持非贪婪匹配。如果要使用非贪婪匹配需要改写规则,如:

# 字符串:"abc:def:ghi:jkl" 
# 匹配目标:取第一列,即"abc" 
# 错误写法:".*:" 
# 正确写法:"[^:]*" 

# 匹配目标:取前两列,即"abc:def"
# 正则表达式:"[^:]*:[^:]*"
量词会产生大量的回溯,回溯会影响正则表达式中的性能。

二选一表达式

基础正则表达式中,需要对 | 进行转义,使用 | 来表示二选一表达式。竖线 | 分隔左右两个正则子表达式,表示匹配任一一个即可。例如 a|b 表示a或者b,在结果上等价于 [ab] ;

[0-5]|\sa 表示0、1、2、3、4、5、" a"。

使用二选一子表达式注意几点:

  1. 二选一元字符优先级很低,所以 abc|def 表示的是abc或def,等价于 (abc)|(def) ,而不是 ab(c|d)ef
  2. 二选一结构和中括号表达式的性能比较:

    • 对于DFA引擎(grep egrep awk sed)来说,二选一结构和中括号的性能是完全一样的,比如

a|b|c|d|e 和 [abcde] 完全等价

  • 对于NFA引擎(编程语言支持的正则表达式全所NFA引擎)来说,二选一结构性能远低于中括号性

能, a|b|c|d|e 意味着5倍的回溯数量,而 [abcde] 它只有单次回溯数量

能用中括号表达式就不要用二选一结构

把尽可能匹配到的选择写在二选一结构的前面

  1. 二选一结构在分组捕获时,只有成功匹配时才会能反向引用

    x(abc)|(def)y 中,要么 1 可用,要么 2 可用,不会同时有用

    ([ab])x|cdy1 无法匹配cdya或cdyb

分组捕获

使用小括号包围一部分正则表达式 (pattern) ,这部分正则表达式即成为一个分组整体,也即成为一个子表达式。小括号有两个隐含的功能:

  1. 分组
  2. 自动捕获:保存分组的匹配结果,以后可以去引用这个捕获的结果

根据左括号的位置决定第几个分组。

例如: (abc)def 、 ([a-d] ){3} 、 (([0-9])abc(def){2})(hgi)

分组后可以使用 N 来反向引用对应的分组的匹配结果,N是1-9的正整数, 1 表示第一个分组表达式的匹配结

果, 2 表示第二个分组表达式的匹配结果。

匹配模式修饰符

​ i修饰符:忽略大小写的匹配 "abcABC" "/ab/i"

​ g修饰符:全局匹配

等价类和排序类

  • [=x=] 包围时表示等价类(equivalence class),等价类表示将普通字母和带有重音的字母分为一类。例如

aāáǎà 是一类,它等价于 [=a=] 、 oōóǒò 又是一类,等价于 [=o=] 。注:没有 [=ao=] 这样连在一起的类。

  • [.xyz.] 包围时表示排序类(collating symbol),排序类表示将 [..] 包围中的多个符号当作一个字符,但要

求在字符集中预先定义映射关系,例如已经预定义了xyz对应于R,那么 a[[.xyz.]]a 可以可以匹配 aRa 。

其他

贪婪匹配与非贪婪匹配

在编程语言中的正则支持贪婪匹配,使用 .*?可以尽可能少的匹配字符。如果在末尾,那么非贪婪匹配将没有效果

image-20200926192911035

匹配过程与回溯

我的名片

昵称:shuta

职业:后台开发(python、php)

邮箱:648949076@qq.com

站点信息

建站时间: 2020/2/19
网站程序: ANTD PRO VUE + TP6.0
晋ICP备18007778号