4.3 捕获分组和后向引用

当一个模式的全部或者部分内容由一对括号分组时,它就对内容进行捕获并临时存储于内存中。可以通过后向引用重用捕获的内容,形式为:

  1. \1

  1. $1

这里\1$1引用的是第一个捕获的分组,而\2$2引用第二个捕获的分组,以此类推。sed只接受\1这种形式,而Perl则两种都接受。

最开始sed支持范围从\1\9的后向引用,但现在已经没有这样的限制了。

下面再展示一下后向引用的使用方法。我们将使用它来重新排序诗文中的一行词,在此先跟作者柯勒律治道个歉。点击RegExr的Repalce标签,在顶部的文本框中输入下面的模式:

  1. (It is) (an ancyent Marinere)

向下滚动主文本框(第三个文本区)直到你可以看到被标亮的那一行,然后在第二个文本框中,输入:

  1. $2 $1

就可以看到在最下面的文本框中那一行被重新排列为:

  1. an ancyent Marinere It is,

结果参见图4-2。

enter image description here

图4-2 用$1$2进行后向引用

要用sed得到相同结果,可以这样做:

  1. sed -En 's/(It is) (an ancyent Marinere)/\2 \1/p' rime.txt

输出为:

  1. an ancyent Marinere It is,

和RegExr中一样。为了帮助你理解每个细节,下面我们来分析一下这个sed命令。

  • -E选项仍然是调用ERE(扩展的正则表达式),因此,括号可以直接当成字面值来使用了。
  • -n选项覆盖打印每一行的默认设置。
  • 替换命令搜索与文本“It is an ancyent Marinere,”匹配的内容,再将其捕获放入两个分组中。
  • 替换命令还将捕获的文本重排为先是后向引用\2的内容再是\1的内容,再将匹配的文本替换为重排后的内容并输出。
  • 替换命令结尾处的p表示要打印该行。

Perl中类似的命令会做相同的事:

  1. perl -ne 'print if s/(It is) (an ancyent Marinere)/\2 \1/' rime.txt

注意该命令使用了\1风格的语法。当然,你也可以使用$1语法:

  1. perl -ne 'print if s/(It is) (an ancyent Marinere)/$2 $1/' rime.txt

我喜欢Perl允许不必在循环中跳转就能打印指定行的方式。

但关于输出,还有一点要注意的是:

  1. an ancyent Marinere It is,

在转换过程中某些单词首字母的大小写被打乱了。Perl可以使用\u\l来修正这个问题,就是这样:

  1. perl -ne 'print if s/(It is) (an ancyent Marinere)/\u$2 \l$1/' rime.txt

现在结果看起来好多了:

  1. An ancyent Marinere it is,

接下来解释一下原因。

  • \l语法并不匹配任何字符,而是会将紧接其后的字母变为小写。
  • \u语法将紧接其后的字母变为大写。
  • \U指令(未展示)将紧接其后的文本字符串全部变为大写。
  • \L指令(未展示)将紧接其后的文本字符串全部变为小写。

这些指令在文本中出现其他指令(比如\l\E,作为文字使用的字符串的结尾)之前都是起作用的。请自己动手试试这些指令。

命名分组

命名分组(named group)就是有名字的分组。这样,就可以通过名字(而不是数字)来引用分组。下面展示一下Perl语言中如何使用命名分组:

  1. perl -ne 'print if s/(?<one>It is) (?<two>an ancyent Marinere)/\u$+{two}\l$+{one}/' rime.txt

我们来看看这个命令:

  • 在括号内添加??将分组分别命名为one和two;
  • $+{one}引用名为one的分组,而$+{two}则引用名为two的分组。

如果在一个模式中有分组被命名,那么你还可以重用该命名分组。解释一下这句话的意思,假设你要查找含有连续六个0的字符串:

  1. 000000

这个例子很浅显,但可以说明其工作原理。用这一模式对连续三个0的分组命名(其中z是分组名,可以任意取):

  1. (?<z>0{3})

然后你可以再使用该分组,就像这样:

  1. (?<z>0{3})\k<z>

或者:

  1. (?<z>0{3})\k'z'

或者:

  1. (?<z>0{3})\g{z}

请在RegExr中尝试一下看看结果。这些示例应该都能用。表4-3列出了命名分组可能使用的各种语法。

表4-3 命名分组的语法

语法描述
(?<name>…) 命名分组
(?name…) 另一种命名分组的方式
(?P<name>…) Python中的命名分组
\k<name>在Perl中引用分组名
\k'name' 在Perl中引用分组名
\g{name} 在Perl中引用分组名
\k{name} 在.NET中中引用分组名
(?P=name) 在Python中中引用分组名