PHP 日本大会 2024

反向引用

在字符类之外,反斜杠后跟大于 0 的数字(可能还有其他数字)是对模式中前面(即左侧)捕获子模式的反向引用,前提是前面已经有那么多捕获的左括号。

但是,如果反斜杠后的十进制数字小于 10,它总是被视为反向引用,并且只有在整个模式中没有那么多捕获的左括号时才会导致错误。换句话说,引用的括号不需要位于引用的左侧(对于小于 10 的数字)。当涉及重复并且右侧的子模式参与了之前的迭代时,“向前反向引用”是有意义的。有关反斜杠后数字处理的更多详细信息,请参阅转义序列部分。

反向引用匹配当前主题字符串中实际匹配捕获子模式的内容,而不是匹配子模式本身的任何内容。因此,模式(sens|respons)e and \1ibility匹配“sense and sensibility”和“response and responsibility”,但不匹配“sense and responsibility”。如果在反向引用时强制使用区分大小写(区分大小写)匹配,则字母的大小写很重要。例如,((?i)rah)\s+\1匹配“rah rah”和“RAH RAH”,但不匹配“RAH rah”,即使原始捕获子模式是区分大小写(不区分大小写)匹配的。

可能有多个对同一子模式的反向引用。如果子模式在特定匹配中实际上没有使用,则对它的任何反向引用总是失败。例如,模式(a|(bc))\2如果它开始匹配“a”而不是“bc”,则总是失败。因为可能有最多 99 个反向引用,所以反斜杠后的所有数字都被视为潜在的反向引用编号的一部分。如果模式继续使用数字字符,则必须使用一些分隔符来终止反向引用。如果设置了PCRE_EXTENDED选项,则可以是空格。否则可以使用空注释。

在它所引用的括号内出现反向引用时,当子模式首次使用时会失败,例如,(a\1) 从不匹配。但是,此类引用在重复的子模式中很有用。例如,模式(a|b\1)+匹配任意数量的“a”,以及“aba”、“ababba”等。在子模式的每次迭代中,反向引用都匹配与前一次迭代相对应的字符字符串。为了使这能工作,模式必须是这样的:第一次迭代不需要匹配反向引用。这可以使用选择来完成,如上面的示例所示,或者通过具有最小值为零的量词来完成。

\g转义序列可用于对子模式进行绝对和相对引用。此转义序列后必须跟一个无符号数或负数,可选地用大括号括起来。\1\g1\g{1}是同义词。将此模式与无符号数一起使用有助于消除使用反斜杠后数字时固有的歧义。该序列有助于区分反向引用和八进制字符,还可以更容易地让反向引用后跟一个文字数字,例如\g{2}1

使用带有负数的\g序列表示相对引用。例如,(foo)(bar)\g{-1}将匹配序列“foobarbar”,而(foo)(bar)\g{-2}匹配“foobarfoo”。这在长模式中作为跟踪子模式数量以引用特定先前子模式的替代方法很有用。

对命名子模式的反向引用可以通过(?P=name)\k<name>\k'name'\k{name}\g{name}\g<name>\g'name'实现。

添加注释

用户贡献的注释 2 条注释

12
mnvx at yandex dot ru
8 年前
类似的机会是 DEFINE。

示例
(?(DEFINE)(?<myname>\bvery\b))(?&myname)\p{Pd}(?&myname).

上面的表达式将从下一句中匹配“very-very”
Define 非常非常方便。
^-------^

它是如何工作的。(?(DEFINE)(?<myname>\bvery\b)) - 此块将“myname”定义为“\bvery\b”。因此,此块“(?&myname)\p{Pd}(?&myname)”等效于“\bvery\b\p{Pd}\bvery\b”。
1
Steve
2 年前
作为反向引用使用的转义序列 \g 并不总是按预期工作。
以下编号的反向引用指的是与指定捕获组匹配的文本,如文档中所述
\1
\g1
\g{1}
\g-1
\g{-1}

但是,以下变体指的是子模式代码而不是匹配的文本
\g<1>
\g'1'
\g<-1>
\g'-1'

对于命名反向引用,我们也可以使用 \k 转义序列以及 (?P=...) 结构。以下组合也指的是与命名捕获组匹配的文本,如文档中所述
\g{name}
\k{name}
\k<name>
\k'name'
(?P=name)

但是,这些指的是子模式代码而不是匹配的文本
g<name>
\g'name'

在下面的示例中,捕获组搜索单个字母“a”或“b”,然后反向引用搜索相同的字母。因此,这些模式预期匹配“aa”和“bb”,但不匹配“ab”或“ba”。

<?php
/* 在目标字符串 'aa ab ba bb' 中,匹配以下模式的字符将被替换为 'xx'。 */
$patterns = [
# 数字化的反向引用(绝对)
'/([ab])\1/', // 'xx ab ba xx'
'/([ab])\g1/', // 'xx ab ba xx'
'/([ab])\g{1}/', // 'xx ab ba xx'
'/([ab])\g<1>/', // 'xx xx xx xx' # 预期之外的行为,反向引用同时匹配 'a' 和 'b'
"/([ab])\g'1'/", // 'xx xx xx xx' # 预期之外的行为,反向引用同时匹配 'a' 和 'b'
'/([ab])\k{1}/', // 'aa ab ba bb' # 没有名为 "1" 的分组,对未设置的分组的反向引用始终失败。
'/([ab])\k<1>/', // 'aa ab ba bb' # 没有名为 "1" 的分组,对未设置的分组的反向引用始终失败。
"/([ab])\k'1'/", // 'aa ab ba bb' # 没有名为 "1" 的分组,对未设置的分组的反向引用始终失败。
'/([ab])(?P=1)/', // NULL # 正则表达式错误:"子模式名称必须以非数字字符开头",(?P=) 期望名称而非数字。
# 数字化的反向引用(相对)
'/([ab])\-1/', // 'aa ab ba bb'
'/([ab])\g-1/', // 'xx ab ba xx'
'/([ab])\g{-1}/', // 'xx ab ba xx'
'/([ab])\g<-1>/', // 'xx xx xx xx' # 预期之外的行为,反向引用同时匹配 'a' 和 'b'
"/([ab])\g'-1'/", // 'xx xx xx xx' # 预期之外的行为,反向引用同时匹配 'a' 和 'b'
'/([ab])\k{-1}/', // 'aa ab ba bb' # 没有名为 "-1" 的分组,对未设置的分组的反向引用始终失败。
'/([ab])\k<-1>/', // 'aa ab ba bb' # 没有名为 "-1" 的分组,对未设置的分组的反向引用始终失败。
"/([ab])\k'-1'/", // 'aa ab ba bb' # 没有名为 "-1" 的分组,对未设置的分组的反向引用始终失败。
'/([ab])(?P=-1)/', // NULL # 正则表达式错误:"期望子模式名称",(?P=) 期望名称而非数字。
# 命名的反向引用
'/(?<name>[ab])\g{name}/', // 'xx ab ba xx'
'/(?<name>[ab])\g<name>/', // 'xx xx xx xx' # 预期之外的行为,反向引用同时匹配 'a' 和 'b'
"/(?<name>[ab])\g'name'/", // 'xx xx xx xx' # 预期之外的行为,反向引用同时匹配 'a' 和 'b'
'/(?<name>[ab])\k{name}/', // 'xx ab ba xx'
'/(?<name>[ab])\k<name>/', // 'xx ab ba xx'
"/(?<name>[ab])\k'name'/", // 'xx ab ba xx'
'/(?<name>[ab])(?P=name)/', // 'xx ab ba xx'
];

foreach (
$patterns as $pat)
echo
" '$pat',\t// " . var_export(@preg_replace($pat, 'xx', 'aa ab ba bb'), 1) . PHP_EOL;
?>
To Top