正则表达式——选择、分组和后向引用

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/)这样的非回溯引擎可彻底关闭回溯操作,而使用原子分组可以关闭正则表达式的部分回溯操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值