在正则表达式中,反向引用(backreference)、零宽断言(zero-width assertion)、负向零宽断言(negative zero-width assertion)是高级的匹配技术,用于更精细地指定匹配规则,而不是简单地匹配字符或字符集。
1. 反向引用(Backreference)
反向引用允许在正则表达式中引用前面捕获的子组(或子模式)。它允许在同一个正则表达式中重复使用先前匹配的文本。通常用 \n 表示,其中 n 是一个数字,表示对第 n 个捕获组的引用。
语法和示例:
- 使用括号
()捕获子组。 - 使用
\n在正则表达式中引用捕获的子组,其中\n是对应的子组编号。
例如,正则表达式 (a+)\1 匹配连续出现的字母 a,如 aa、aaa、aaaa 等。在这里,\1 表示引用第一个捕获组 (a+) 所匹配的内容。
2. 零宽断言(Zero-Width Assertions)
零宽断言是一种特殊的匹配条件,用于在不消耗字符的情况下(零宽度),仅仅判断当前位置是否满足某种条件。
常见的零宽断言有:
- 正向先行断言(Positive Lookahead):
(?=…)- 正向先行断言匹配在某些内容之前的位置,而不匹配这些内容本身。它在当前位置向前检查是否能匹配括号中的表达式。
示例: 匹配包含数字和字母的字符串,但只匹配数字前的部分。
\d(?=\w)这个正则表达式
\d(?=\w)匹配一个数字后面跟着一个字母或数字。 - 负向先行断言(Negative Lookahead):
(?!…)- 负向先行断言匹配在某些内容之前的位置,但这些内容不能匹配括号中的表达式。
示例: 匹配不包含特定字符序列的单词。
\b(?!bad\b)\w+\b这个正则表达式
\b(?!bad\b)\w+\b匹配不是bad的单词。
前瞻示例
- 正前瞻:
\w+(?=ing\b),匹配以“ing”结尾的单词中的前面部分(不包含“ing”)。 - 负前瞻:
\b(?!bad\b)\w+\b,匹配不等于“bad”的单词。
后顾示例
- 正后顾:
(?<=\$)\d+,匹配以$符号开头的数字(不包含$)。 - 负后顾:
(?<!\$)\d+,匹配前面没有$符号的数字。
3. 负向零宽断言(Negative Zero-Width Assertions)
负向零宽断言是一种特殊的零宽断言,它用来指定当前位置不应该匹配某些内容。
常见的负向零宽断言有:
- 负向后行断言(Negative Lookbehind):
(?<!…)- 负向后行断言匹配当前位置之前的内容,但这些内容不应该匹配括号中的表达式。
示例: 匹配不在引号内的单词。
(?<!")\b\w+\b(?!")这个正则表达式
(?<!")\b\w+\b(?!")匹配不在双引号内的单词。
平衡组以及递归匹配
<[^<>]*(((?'Open'<)[^<>]*)+((?'-Open'>)[^<>]*)+)*(?(Open)(?!))>
这段正则表达式是用来匹配最外层的 <...> 形式的标签,并确保标签的嵌套结构是正确闭合的。让我解释一下它的各个部分:
-
<[^<>]*: 匹配最外层的左尖括号<开始的部分,然后跟着零个或多个非尖括号字符[^<>]*,表示匹配标签的开始部分。 (开始的部分:(?'Open'<): 定义一个命名捕获组Open,它匹配一个左尖括号<,并将其命名为Open。[^<>]*: 匹配左尖括号后面的任意非尖括号字符。
-
)+表示这个组合模式可以重复一次或多次,以处理标签内部的可能的嵌套。 (开始的部分:(?'-Open'>): 定义一个命名捕获组Open,它匹配一个右尖括号>,并尝试从堆栈中弹出一个名为Open的左尖括号。[^<>]*: 匹配右尖括号后面的任意非尖括号字符。
-
)+表示这个组合模式可以重复一次或多次,以确保所有打开的左尖括号都有相应的右尖括号来闭合。 -
*: 匹配零个或多个这样的嵌套结构,以处理可能存在多个标签的情况。 -
(?(Open)(?!)): 这是一个条件表达式,用来检查是否还有未匹配的左尖括号。如果堆栈中还有未匹配的Open,则匹配失败。 >: 最外层的右尖括号>,用来匹配标签的结束部分。
这段正则表达式的目的是确保在处理 HTML 标签或类似的结构时,所有的左尖括号 < 都有相应的右尖括号 > 来正确闭合,而且不会有嵌套的标签结构错位。
(?(Open)(?!)) 是正则表达式中的条件表达式,用来检查是否堆栈中还有未匹配的捕获组 Open。
解释:
(?(Open)(?!)):这个结构分为两部分:?(Open):这是条件部分,它检查是否存在名为Open的捕获组。在正则表达式中,捕获组的堆栈类似于一个栈,用于跟踪捕获组的开启和关闭。(?!):这是断言部分,表示否定前向查找。它会尝试匹配当前位置,但只有在紧接着的位置不匹配条件时才成功。换句话说,它要求当前位置的后续内容不匹配Open的条件。
工作原理:
- 条件部分
(?(Open):- 如果存在名为
Open的捕获组,说明在之前的正则表达式中有使用(?'Open'<)来定义一个名为Open的捕获组,并且在后续的模式中可能会有对应的(?'-Open'>)来关闭这个捕获组。
- 如果存在名为
- 断言部分
(?!):- 如果条件部分
(?(Open)成立(即存在未关闭的Open捕获组),则断言部分(?!)将尝试在当前位置匹配失败。这意味着如果在当前位置后面的内容中继续有<字符(即未关闭的标签),则整个正则表达式将不匹配。
- 如果条件部分
实际应用:
在处理嵌套结构(如 HTML 标签)时,这种技术可以确保所有打开的标签都有对应的闭合标签。如果条件部分 (?(Open) 检测到还有未匹配的打开标签 Open,那么断言部分 (?!) 将强制匹配失败,因为当前位置后面的内容与期望的闭合标签不匹配,从而使整个正则表达式不匹配。
这种技术在正则表达式中的应用,使得能够更加精确和有效地处理复杂的嵌套结构,如处理文本中的嵌套括号、HTML 标签等情况。
前瞻后顾一些误区
(?<=\$)\d+ 和 (?=\$)\d+ 表达的意思是不同的,具体区别如下:
1. (?<=\$)\d+(正后顾)
- 解释:匹配在美元符号
$后面的数字,但不包括$本身。 - 实例:匹配字符串
"Total is $100"时,(?<=\$)\d+会匹配100,因为它检查的是$后面的数字。
2. (?=\$)\d+(正前瞻,错误)
- 解释:这个正则表达式的意思是匹配前面是
$的数字,但不包含$本身。 - 实例:匹配字符串
"100$"时,(?=\$)\d+会匹配100,但它检查的是数字前面是否有$,而不是后面。
3. 两者的区别
(?<=\$)是后顾,它是从当前位置向后看,确认前面是$。(?=\$)是前瞻,它是从当前位置向前看,确认后面是$。
总结
(?<=\$)\d+:匹配在$符号后面的数字。(?=\$)\d+:匹配后面是$符号的数字。
因此,(?=\$)\d+ 是有效的,但它的含义和 (?<=\$)\d+ 不同。
是的,你的理解是正确的!如果你想匹配在 $ 符号前面的数字,应该写成 \d+(?=\$)。
解释:
\d+:匹配一个或多个数字。(?=\$):这是正前瞻,它检查在数字后面是否跟着一个$符号,但不包含$。
示例:
- 对于字符串
"100$",(\d+(?=\$))会匹配100,因为它确认数字后面紧跟着一个$符号。
综上:
(?<=\$)\d+:匹配$后面的数字。\d+(?=\$):匹配数字并确保后面紧跟着$符号。
这两个正则表达式是针对不同的需求的。如果你的目标是匹配数字前面没有 $,就使用 (?=\$);如果要确保匹配的数字后面是 $,就使用 (?<=\$)。