选择、分组和后向引用
1、选择操作
简单地说,选择操作可在多个可选模式中匹配一个。例如,你想在“The Rime of the Ancyent Mariner”中找出the出现过多少次,包括THE、The和the等形式。为此,就可以使用选择操作。
在顶部的文本框中输入:
(the|The|THE)

可以使用一个选项来使分组更简短。借助选项,可以指定查找模式的方式。例如,选项
(?i)
让你的模式不再区分大小写。因此原来带选择操作的模式可以简写成
(?i)the

还可以在RegExr中勾选ignoreCase来设定不区分字母大小写,这两种方式都可以。下表中列出了以上选项以及其他各种选项和修饰符。

接下来我们在grep中使用选择操作。表中的选项并不适用于grep,所以你要使用原来的选择模式。要对单词the出现一次或多次的行的数目进行统计,且不区分大小写,则用:
grep -Ec "(the|The|THE)" rime2.txt
可以得到这个结果:
327
这个结果还不能说明一切。欲知原因,请继续阅读。
以下是对上面的grep命令的分析。
- -E选项表示使用扩展的正则表达式(ERE),而不用基本正则表达式(BRE)。本例省去了将括号和竖线符转义的步骤,如果使用BRE则必须进行转义,像这样(THE|The|the)。
- -c选项返回匹配的行数(不是匹配的单词)。
- 括号将对the、The以及THE的选择操作归为一个分组。
- 竖线符将备选项分开,对备选项求值的顺序是从左至右。
下面的方法会将单词的每一次出现都作为一行内容返回,这样就可以得到实际的出现次数:
grep -Eo "(the|The|THE)" rime2.txt | wc -l
这个命令返回的是:
412
以下是对命令的分析。
- -o选项表示只显示一行中与指定模式匹配的部分,而由于导向wc的管道(|),该选项的作用不太直观。
- 在此上下文中,竖线符将grep命令的输出通过管道导向wc命令的输入。wc是单词计数命令,而-l对输入的行数进行统计。
为什么有327与412这样大的差异呢?因为-c给出的是匹配的行的数目,但是一行中可能有多个单词匹配。若使用-o和wc -l,则指定单词不管以哪一种形式出现,每次出现都会作为单独的一行统计,这样结果值就会更大。
要使用Perl执行同种匹配,则这样写:
perl -ne 'print if /(the|The|THE)/' rime2.txt
再进一步改善,就是将i修饰符加在定界符之后:
perl -ne 'print if /the/i' rime2.txt
结果相同,但命令是越简单越好。下表中列出了更多的修饰符(也称为标记符)。

2、子模式
多数情况下,提到正则表达式中的子模式(subpattern),就是指分组中的一个或多个分组。子模式就是模式中的模式。多数情况下,子模式中的条件能得到匹配的前提是前面的模式得到匹配,但也有例外。子模式的写法可以有很多种,这里我们主要关注括号中的子模式。
从某种意义上说,你之前所见的模式:
(the|The|THE)
有三个子模式:the是第一个子模式,The是第二个,而THE是第三个。但是这种情况下,匹配第二个子模式不依赖于是否匹配第一个。(最左边的模式会首先匹配。)
而在以下的模式中,子模式依赖于前面的模式:
(t|T)h(e|eir)

通俗地讲,这个模式会匹配字面值t或T,然后是一个h,接下来就是一个e或者是eir。相应地,这个模式会匹配以下所有情况:
- the
- The
- their
- Their
在以上情况中,第二个子模式(e|eir)依赖于第一个子模式(tT)。
括号对于子模式不是必需的。下面是一个使用字符组的子模式示例:
\b[tT]h[ceinry]*\b

这个模式会匹配the或The还有thee、thy以及thence等单词。两个单词边界(\b)表示该模式只匹配整个单词,而不会匹配单词中的某几个字母。
以下是对这个模式的解析。
\b匹配单词起始边界。[tT]是字符组,它匹配小写字母t或者大写字母T。可以将其看做是第一个子模式。- 然后匹配(或尝试匹配)小写字母h。
- 第二个也就是最后一个子模式也表示为字符组[ceinry],其后用量词*表示零个或多个。
- 最后,该模式以另外一个
\b结束。
3、捕获分组和后向引用
当一个模式的全部或者部分内容由一对括号分组时,它就对内容进行捕获并临时存储于内存中。可以通过后向引用重用捕获的内容,形式为:
\1
或
$1
这里\1或$1引用的是第一个捕获的分组,而\2或$2引用第二个捕获的分组,以此类推。sed只接受\1这种形式,而Perl则两种都接受。
下面再展示一下后向引用的使用方法。我们将使用它来重新排序诗文中的一行词,在此先跟作者柯勒律治道个歉。点击RegExr的Repalce标签,在顶部的文本框中输入下面的模式:
(It is) (an ancyent Marinere)
向下滚动主文本框(第三个文本区)直到你可以看到被标亮的那一行,然后在第二个文本框中,输入:
$2 $1

就可以看到在最下面的文本框中那一行被重新排列为:
an ancyent Marinere It is,
要用sed得到相同结果,可以这样做:
sed -En 's/(It is) (an ancyent Marinere)/\2 \1/p' rime2.txt

和RegExr中一样。为了帮助你理解每个细节,下面我们来分析一下这个sed命令。
- -E选项仍然是调用ERE(扩展的正则表达式),因此,括号可以直接当成字面值来使用了。
- -n选项覆盖打印每一行的默认设置。
- 替换命令搜索与文本“ It is an ancyent Marinere, ”匹配的内容,再将其捕获放入两个分组中。
- 替换命令还将捕获的文本重排为先是后向引用 \2的内容再是\1的内容,再将匹配的文本替换为重排后的内容并输出。
- 替换命令结尾处的p表示要打印该行。
Perl中类似的命令会做相同的事:
perl -ne 'print if s/(It is) (an ancyent Marinere)/\2 \1/' rime2.txt

注意该命令使用了\1风格的语法。当然,你也可以使用$1语法:
perl -ne 'print if s/(It is) (an ancyent Marinere)/$2 $1/' rime2.txt

但关于输出,还有一点要注意的是:
an ancyent Marinere It is,
在转换过程中某些单词首字母的大小写被打乱了。Perl可以使用\u和\l来修正这个问题,就是这样:
perl -ne 'print if s/(It is) (an ancyent Marinere)/\u$2 \l$1/' rime2.txt

接下来解释一下原因。
\l语法并不匹配任何字符,而是会将紧接其后的字母变为小写。\u语法将紧接其后的字母变为大写。\U指令(未展示)将紧接其后的文本字符串全部变为大写。\L指令(未展示)将紧接其后的文本字符串全部变为小写。
这些指令在文本中出现其他指令(比如\l或\E,作为文字使用的字符串的结尾)之前都是起作用的。请自己动手试试这些指令。
命名分组:
命名分组(named group)就是有名字的分组。这样,就可以通过名字(而不是数字)来引用分组。下面展示一下Perl语言中如何使用命名分组:
perl -ne 'print if s/(?<one>It is) (?<two>an ancyent Marinere)/\u$+{two}
\l$+{one}/' rime2.txt
我们来看看这个命令:
- 在括号内添加
?<one>和?将分组分别命名为one和two; $+{one}引用名为one的分组,而$+{two}则引用名为two的分组。
如果在一个模式中有分组被命名,那么你还可以重用该命名分组。解释一下这句话的意思,假设你要查找含有连续六个0的字符串:
000000
这个例子很浅显,但可以说明其工作原理。用这一模式对连续三个0的分组命名(其中z是分组名,可以任意取):
(?<z>0{3})

然后你可以再使用该分组,就像这样:
(?<z>0{3})\k<z>
或者:
(?<z>0{3})\k'z'
或者:
(?<z>0{3})\g{z}
下表列出了命名分组可能使用的各种语法。

4、非捕获分组
还有一种分组是非捕获分组(Non-CapturingGroup)。非捕获分组不会将其内容存储在内存中。在你并不想引用分组的时候,可以使用它。由于不存储内容,非捕获分组就会带来较高的性能。
还记得第一个分组吗?就是这个:
(the|The|THE)
你不需要任何后向引用,因此可以这样写一个非捕获分组:
(?:the|The|THE)
你可以添加选项将其变为不区分大小写的模式,就像这样:
(?i)(?:the)
或者你也可以这样做:
(?:(?i)the)
不过,下面这种写法最值得推荐:
(?i:the)

该选项i可以放在问号和冒号之间。
原子分组:
另一种非捕获分组是原子分组(atomic group)。如果你使用的正则表达式引擎进行回溯操作,这种分组就可以将回溯操作关闭,但它只针对原子分组内的部分,而不针对整个正则表达式。其语法如下:
(?>the)
什么时候你会想使用原子分组呢?正则表达式处理过程缓慢的一个因素就是回溯操作。其原因就是回溯操作会尝试每一种可能性,这会消耗时间和计算资源。有时它会占用大量时间。回溯有可能产生巨大的负面效应,这被称为灾难性回溯。
使用像re2(http://code.google.com/p/re2/)这样的非回溯引擎可彻底关闭回溯操作,而使用原子分组可以关闭正则表达式的部分回溯操作。

473

被折叠的 条评论
为什么被折叠?



