正则表达式——捕获组与非捕获组



  • 1. 基本概念

    正则表达式是一个由一些普通字符和一些元字符组成的字符串,这个字符串在正则引擎的作用下,可以用来匹配字符串。

    正则匹配的流程与两个字符串严格匹配的过程类似。假设它们为 str, pattern,匹配的大致流程如下:

    1. str 和 pattern 左对齐
    2. 判断是否匹配
    3. 如果匹配,匹配长度为 len, 则 pattern 向右滑动 len 个单位,重复 2
    4. 如果不匹配,pattern 向右滑动 1 个单位,重复 2
    5. 匹配时出现下标越界或滑动时出现下标越界则匹配结束

    理解这个流程对于我们理解后面的内容有很大的帮助,记住一点,正则表达式是沿字符串不断向右滑动的,匹配的时候也是从最左往右匹配。

    2. 捕获组 ( )

    在正则表达式的某一子表达式两边加上一对括号,可以创建一个捕获组,用于获取匹配该子表达式的字符串。

    这种用法非常常见,因为大多数时候一短文本里只有一小部分是我们需要的信息,但是我们又需要靠一些额外的信息来定位我们需要的信息,这时我们写的正则表达式匹配到的数据就分为了两部分,一部分是需要的,一部分是不需要的。为了只获取需要的信息,我们可以为这个信息创建一个捕获组。

    举个栗子栗子:

    username=hehe
    password=haha
    

    如果我们想解析上面文本中 username 字段对应的值,可以使用下面这个正则表达式

    username=(\w+)
    

    如果我们使用 username=\w+ ,匹配得到的字符串是 username=hehe

    在正则里加入一个括号,就创建了一个匹配组,我们可以通过这个匹配组的编号来获取匹配组里匹配到的内容。username=(\w)+ 匹配得到的字符串也是 username=hehe, 但它还有一个编号为 1 的匹配组,其内容为 hehe

    再看一个例子:
    string: "abc"
    pattern: "(a)(b)(c)"

    使用 (a)(b)(c) 作为 pattern 来匹配字符串 "abc" 的时候会产生三个捕获组,编号分别为 1, 2, 3, 值分别为 a, b, c。编号为 0 的 group 表示这个 pattern 匹配的整个字符串,这个一般不包括在匹配组里,即 groups() 返回的是编号不为 0 的捕获组。这样设计的原因是,在 pattern 里加括号,表示我们对括号里的字符串比较感兴趣,而不是整体,如果只想匹配整体的话,中间就没必要加括号了。

    以下是一段用于验证的 python 程序。

    s = "abc"
    pattern = r"(a)(b)(c)"
    m = re.search(pattern, s)
    print('groups:', m.groups())
    print('group(0):', m.group(0))
    print('group(1):', m.group(1))
    print('group(2):', m.group(2))
    print('group(3):', m.group(3))
    
    输出:
    groups: ('a', 'b', 'c')
    group(0): abc
    group(1): a
    group(2): b
    group(3): c
    

    3. 非捕获组 (? )

    非捕获组与捕获组的区别在于,非捕获组在左括号 "(" 右边加了一个问号 "?",并且括号内匹配的内容不会被当做一个捕获组,没有捕获组编号。

    3.1 简单非捕获组

    (?:pattern)
    匹配 pattern 而不将其加入捕获组。

    string: "abc"
    pattern: "(?:a)(b)(c)"
    这个表达式匹配得到的字符串是 "abc"(即 group(0) = abc)。
    有两个捕获组,group(1) = a, group(2) = b。

    3.2 零宽断言

    先解释几个词:
    Positive/Negative: 肯定/否定
    Lookahead/Lookbehind: 前向/后向
    Zero-Length: 零长度(零宽)
    Assertion: 断言

    再解释几个符号:
    = 表示肯定
    ! 表示否定
    < 表示负向(什么都没有表示正向)

    把肯定、否定、正向、负向组合,就有了以下四种零宽断言的形式。

    3.2.1 肯定正向零宽断言 (Positive Lookahead Zero-Length Assertion)。

    pattern1(?=pattern2)

    在匹配到 pattern1 后,向右(正向) 匹配 pattern2,如果后面的字符串匹配 pattern2,则 pattern1 匹配成功,否则 pattern1 匹配失败。pattern2 是零(长度)宽断言,因此 pattern2 是不占长度的,即匹配后面的字符串的时候,是从匹配 pattern1 的字符串结尾的下一个字符开始匹配。

    pattern: "(ab)c(?=-)"
    对于字符串 "abc-abc", 匹配结果是第一个 "abc",有一个捕获组 "ab"。
    对于字符串 "abc-abc-", 匹配结果是 ("abc", "abc"),有两个捕获组 ("ab", "ab")。

    3.2.2 否定正向零宽断言 (Negative Lookahead Zero-Length Assertion)

    pattern1(?!pattern2)

    在匹配到 pattern1 后,向右(正向) 匹配 pattern2,如果后面的字符串匹配 pattern2,则 pattern1 匹配失败, 否则 pattern1 匹配成功。pattern2 是零(长度)宽断言,因此 pattern2 是不占长度的,即匹配后面的字符串的时候,是从匹配 pattern1 的字符串结尾的下一个字符开始匹配。

    string: "a-b"
    pattern: "(\w)(?!-)"
    匹配结果是 "b", 有一个捕获组 "b"。

    3.2.3 肯定负向零宽断言 (Positive Lookbehind Zero-Length Assertion)

    (?<=pattern1)pattern2

    对于当前的字符,向左(负向) 匹配 pattern1,如果 pattern1 匹配成功,则从当前字符开始匹配 pattern2,若 pattern2 匹配成功,则整个表达式匹配成功,否则整个表达式匹配失败;如果 pattern1 匹配失败,则不继续匹配 pattern2,而是认为整个表达式匹配失败,从后一个字符开始继续匹配。

    string: "a-b"
    pattern: "(?<=-)(\w)"
    匹配结果是 "b", 有一个捕获组 "b"。

    3.2.4 否定负向零宽断言 (Negative Lookbehind Zero-Length Assertion)

    (?<!pattern1)pattern2

    对于当前的字符,向左(负向) 匹配 pattern1,如果 pattern1 匹配失败,则从当前字符开始匹配 pattern2,若 pattern2 匹配成功,则整个表达式匹配成功,否则整个表达式匹配失败;如果 pattern1 匹配成功,则不继续匹配 pattern2,而是认为整个表达式匹配失败,从后一个字符开始继续匹配。

    string: "a-b"
    pattern: "(?<!-)(\w)"
    匹配结果是 "a", 有一个捕获组 "a"。

    4. Reference

    https://www.regular-expressions.info

    5. License

    本作品采用知识共享 署名-非商业性使用-相同方式共享 2.5 中国大陆 许可协议进行许可。要查看该许可协议,可访问 http://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 或者写信到 Creative Commons, PO Box 1866, Mountain View, CA 94042, USA。

    原文链接 https://www.jianshu.com/p/37c1f7c58f26


 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

与 Dian 的连接断开,我们正在尝试重连,请耐心等待