在环视中使用捕获组

Use of capture groups within lookarounds

假设我们得到以下字符串:

a, b, c, d
e, f, g, h
i, j, k, l

我希望使用 PCRE 正则表达式将其转换为以下字符串:

ab, ac, ad
ef, eg, eh
ij, ik, il

更一般地,这些字母中的每一个都可以看作是一串单词字符的占位符,并且每行可以有任意多个字母,也可以有任意多行。

如果做不到,能否产生下面的字符串?

a, ab, ac, ad
e, ef, eg, eh
i, ij, ik, il

请在 regex101.com 上使用 "SUBSTITUTION" 工具(可以包括反向引用,例如 )演示您的正则表达式。我特别希望能解释 PCRE 引擎如何遍历字符串。

如果使用 PCRE 正则表达式不能做到这一点,我想解释为什么不能做到。

我问这个问题是为了加深我对 lookarounds 中捕获组如何工作的理解。

这只能通过支持可变宽度后视模式的正则表达式引擎来完成,而 PCRE 则不支持。需要一个可变宽度的 lookbehind 来为每个后续单词引用每行开头的单词。

如果您的正则表达式引擎支持可变宽度后视模式,您可以搜索:

(?<=(\w+),.*)(\w+)|^\w+,\s*

并将匹配项替换为:


演示:https://regex101.com/r/XZhZyW/5/

我想提一下在遇到这种情况时可能采取的行动方案,如此处,需要可变长度正后视,但所使用的正则表达式引擎不支持该操作,但支持可变长度正前瞻,例如 PCRE (PHP)。

我承认写这个答案主要是为了提高我自己对正则表达式引擎如何运作的理解。

基本思路

  • 反转字符串
  • 使用具有相应正前瞻性的正则表达式来执行匹配项替换
  • 反转结果字符串

例子

假设我们希望转换以下字符串:

a, bb, c, d
ee, f, g, h
i, j, kk, l

到字符串:

abb, ac, ad
eef, eeg, eeh
ij, ikk, il

我们先把原来的字符串反转:

d ,c ,bb ,a
h ,g ,f ,ee
l ,kk ,j ,i

然后匹配正则表达式:

(\w+)(?=.*,(\w+)$)|\s+,\w+$ 

并将每个匹配项替换为+,得到:

da ,ca ,bba
hee ,gee ,fee
li ,kki ,ji

最后,反转这些字符串:

abb, ac, ad
eef, eeg, eeh    
ij, ikk, il

PCRE demo

正则表达式执行以下操作:

(\w+)    # match 1+ word chars in cap grp 1 
(?=      # begin a positive lookahead
  .*,    # match 0+ chars (greedily), then ','
  (\w+)  # match 1+ word chars in cap grp 1
  $
)
|
\s+,\w+
$ 

我将字符串 "a, bb, c, d" 中的空格用小表情符号 () 表示,使它们更明显,并显示字符串:

 a , ☺ b b , ☺ c , ☺ d
^

空格现在代表相邻字符之间的区域。 ^ 是正则表达式引擎指针的初始位置。

(\w+)匹配字符串开头的"a"后(下图m表示),"a"保存到捕获组1,正向前瞻开始在那场比赛之后:

 a , ☺ b b , ☺ c , ☺ d
 m^

正向前看,(?=.*,(\w+)$)保存"d"捕获第2组。1由于匹配成功,第一个匹配,"a" 替换为 + #=> "ad" 并且指针移回执行前瞻之前的位置:

 a , ☺ b b , ☺ c , ☺ d
  ^

现在尝试将 (\w+) 与字符串中以第一个逗号开头的部分相匹配。这失败了,正则表达式 \s+,\w+$ 部分也是如此。然后指针前进一个字符:

 a , ☺ b b , ☺ c , ☺ d
    ^

同样失败,指针又向前移动了一位。

 a , ☺ b b , ☺ c , ☺ d
      ^

(\w+) 现在匹配 "bb",保存到捕获组 1,此时:

 a , ☺ b b , ☺ c , ☺ d
       m m^

和以前一样,正向超前保存 "d" 以捕获组 2 和匹配项,"bb" 替换为 + #=> "bbd"

在两次匹配失败后,我们处于:

 a , ☺ b b , ☺ c , ☺ d
              ^

出于与之前相同的原因,"c" 被匹配并替换为 + => "cd",我们现在在这里:

 a , ☺ b b , ☺ c , ☺ d
                ^

后面没有更多要匹配的单词串,但字符串的结尾", d"现在匹配部分正则表达式,\s+,\w+$。然后,该匹配项将替换为 +。然而,这一次,两个捕获组都是空的,因此匹配被替换为一个空字符串。

1 需要逗号。没有它,贪婪的 .* 会吞噬最后一个字符之前的所有内容。例如,如果字符串以 ", cd" 结尾,则捕获组 2 将仅包含 "d".