1. 基本概念
正则表达式是一个由一些普通字符和一些元字符组成的字符串,这个字符串在正则引擎的作用下,可以用来匹配字符串。
正则匹配的流程与两个字符串严格匹配的过程类似。假设它们为 str, pattern,匹配的大致流程如下:
- str 和 pattern 左对齐
- 判断是否匹配
- 如果匹配,匹配长度为 len, 则 pattern 向右滑动 len 个单位,重复 2
- 如果不匹配,pattern 向右滑动 1 个单位,重复 2
- 匹配时出现下标越界或滑动时出现下标越界则匹配结束
理解这个流程对于我们理解后面的内容有很大的帮助,记住一点,正则表达式是沿字符串不断向右滑动的,匹配的时候也是从最左往右匹配。
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。
网友评论