边界
待匹配的文本:
js
THE RIME OF THE ANCYENT MARINERE, IN SEVEN PARTS.
ARGUMENT.
How a Ship having passed the Line was driven by Storms to the cold Country towards the South Pole; and how from thence she made her course to the tropical Latitude of the Great Pacific Ocean; and of the strange things that befell; and in what manner the Ancyent Marinere came back to his own Country.
I.
It is an ancyent Marinere,
And he stoppeth one of three:
"By thy long grey beard and thy glittering eye
"Now wherefore stoppest me?
1、行的起始与结束
就像之前看到的那样,要匹配行或字符串的起始要使用脱字符(U+005E):
bash
^

根据上下文,^会匹配行或者字符串的起始位置,有时还会匹配整个文档的起始位置。而上下文则依赖于应用程序和在应用程序中所使用的选项。
如你所知,若要匹配行或字符串的结尾位置要使用美元符:
js
$

请确定RegExr中的multiline(多行)选项被勾选。global(全局)选项在打开RegExr时默认勾选,但是本例中你可以勾选也可以不勾选该选项。如果不勾选multiline选项,整个目标文本被视做一个字符串。
在上方的文本框中键入下面这个正则表达式:
js
^How.*Country\.$

它会匹配以单词How开头的整行。请注意结尾的点号之前有一个反斜杠,它对点号进行转义,这样点号就被解释为字面值。如果不对点号转义,它就会匹配任意字符。如果想匹配作为字面值的点号,则必须将点号转义或者将其放入字符组中。
如果不勾选multiline选项会是什么情况?高亮显示功能会被关闭。在不勾选multiline但选dotall的情况下,键入:
js
^THE.*\?$
可以看到它匹配了整个文本。
dotall选项表示点号除了匹配其他字符之外,还会匹配换行符。取消勾选dotall选项,则该表达式什么也不匹配。而以下表达式:
js
^THE.*

则匹配第一行。再点击dotall选项,全部文本都会被匹配。不需要使用? $来匹配文本的结尾。
2、单词边界与非单词边界
我们已经多次遇到使用\b的情况了。它匹配单词边界。请尝试:
js
\bTHE\b

(在勾选global选项的情况下)这个表达式会匹配第一行的两个THE。就像^和$一样,\b是个零宽度断言,表面上它会匹配空格或者是行起始,而实际上它匹配的是个零宽度的不存在的东西。你有没有注意到第二个THE两边的空格是没有标亮的?这是因为它们不是匹配的部分。这个理解起来不是很容易,但你可以通过观察它匹配和不匹配的内容来理解。
你还可以匹配非单词边界。非单词边界匹配除单词边界之外的位置,比如单词或者字符串中的字母或数字。要匹配一个非单词边界,试一下:
js
\Be\B

看看它匹配了什么。可以看到它匹配了小写字母e,而匹配的字母e的两边都是其他字母或者是非单词字符。零宽度断言不会匹配两边的字符,但它会识别文字e的两边是否是非单词边界。
在有些应用程序中,指定单词边界的另一种方法是使用:
js
\<
来指定单词的开头,而使用
js
\>
来指定单词结尾。这是比较旧的语法,在很多最新的正则表达式应用程序中无法使用。但在有些情况下,这种语法就很有用,因为它不会像\b那样匹配任意单词边界,而是允许你分别匹配单词的开头或结尾。
如果你的系统上有vi或者vim,可以用这类编辑器试一下。即使你没用过vim,这也很简单,只要按照以下步骤做即可。在命令行或者shell窗口中,将路径改为诗文所在的地方,再用这个命令打开:
js
vim rime.txt
然后输入以下查找命令:
js
/\>

再按回车键(Enter)或返回键(Return)。在vim中使用斜杠符(/)开始一次查找。请观察光标,可以看到本次查找过程会找到单词的结尾。按下按键n可重复查找。再输入:
js
/\<

再按回车键(Enter)或返回键(Return)。这次查找会找出单词的开头。要退出vim,键入ZZ即可。
这一语法也可用于grep。自从20世纪70年代,grep像sed一样一直是Unix系统的主要工具。(20世纪80年代,我一个同事的车牌上面就写着GREP。)在shell提示符中试试这个命令:
bash
grep -Eoc '\<(THE|The|the)\>' rime2.txt
- -E选项表示要使用扩展的正则表达式(ERE),而不是grep默认使用的基本正则表达式(BRE)。
- -o选项代表结果只显示一行中与模式相匹配的那部分。
- -c选项的意思是只返回结果的数量。单引号中的模式会对THE、The或者the进行整词匹配。这就是<和 >帮你寻找的。
这个命令返回的是:
js
259
也就是找到的单词的数目。
另一方面,如果不使用<和>,结果则不同。这样做:
bash
grep -Eoc '(THE|The|the)' rime2.txt
得到的数字就是:
js
327
为什么?因为该模式只匹配整词和任意包含该词的字符序列。这就是<和>能派上用场的一个原因。
3、其他锚位符
与锚位符^相似,以下简写式匹配主题词的起始:
js
\A

这个写法不是在所有的正则表达式程序中都可以使用的,但可以在Perl和PCRE中使用。要匹配主题词的结尾,可以使用:
js
\Z
在某些上下文中还可用:
js
\z
pcregrep是带有PCRE库的grep版本。安装之后,要使用以上语法,则这样写:
bash
pcregrep -c '\A\s*(THE|The|the)' rime2.txt
单词the出现在行首附近位置且之前有(一个或多个)空格的次数为108次,命令-c会返回这个次数。接下来输入命令:
bash
pcregrep -n '(MARINERE|Marinere)(.)? \Z' rime.txt
这一命令会匹配一行(主题词)尾部的MARINERE或Marinere,之后是任何可选字符,在本例中可选字符就是标点符号或者字母S。(点号两边的括号不是必需的。)
可以看到输出为:
js
1:THE RIME OF THE ANCYENT MARINERE,
10: It is an ancyent Marinere,
38: The bright-eyed Marinere.
63: The bright-eyed Marinere.
105: "God save thee, ancyent Marinere!
282: "I fear thee, ancyent Marinere!
702: He loves to talk with Marineres
pcregrep的-n选项在输出的每行起始处显示行号。pcregrep与grep的命令行选项十分相似。要了解所有选项,输入:
bash
pcre --help
4、使用元字符的字面值
可以用
js
\Q
和
js
\E
之间的字符集匹配字符串字面值。
为了展示这一点,在RegExr下方文本框中输入以下元字符:
js
.^$*+? |(){}[]\-
这15个元字符在正则表达式中有特殊含义,用来编写匹配模式。(连字符在字符组的方括号中用来表示范围。但在其他情况下,则无特殊含义。)
如果你在RegExr上方的文本框中尝试匹配这些字符,则不会看到任何结果。为什么呢?因为RegExr会认为这是个正则表达式而不是字符串字面值。现在试一下
js
\Q$\E

它将匹配$,因为\Q和\E之间的任意字符都会被解释为普通字符。(记住,可以在元字符之前加一个\使其匹配字面值。)
5、添加标签
在RegExr中,取消勾选global但勾选multiline,点击Replace标签,然后在第一个文本框中输入:
js
^(.*)$
这会匹配第一行文本并将其捕捉。然后在下一个文本框(标号为2)中输入以下内容:
html
<! DOCTYPE html>\n<html lang="en">\n<head><title>Rime</title></head>\n
<body>\n<h1>$1</h1>

输入替换文本的时候,你会注意(主题词文本在显示结果的文本框(标号为4)中变了,包含了你刚刚添加的标记。
RegExr很好地展示了添加标记的一种方式,但它也有自己的局限性,比如说它不能将结果保存为文件。因此,我们不能把目光仅局限于浏览器。
5.1、使用sed添加标签
在RegExr中能完成的工作,完全可以使用sed在命令行环境下做到。sed中的插入命令(i)允许你在文件或字符串中的某个位置之前插入文本。而与i命令相反的是命令a,它在某个位置之后添加文本(后面会用到a)。
以下命令从第1行开始插入HTML5的doctype(文档类型)和其他标记:
js
sed '1 i\
<! DOCTYPE html>\
<html lang=\"en\">\
<head>\
<title>Rime</title>\
</head>\
<body>
s/^/<h1>/
s/$/<\/h1>/
q' rime.txt
行尾的反斜杠(\)允许你在该流中插入新行而不会提前执行命令。引号前的反斜杠将引号转义为字面值。
正确运行这个sed命令会得到如下输出:
js
<! DOCTYPE html>
<html lang="en">
<head>
<title>The Rime of the Ancyent Mariner (1798)</title>
</head>
<body>
<h1>THE RIME OF THE ANCYENT MARINERE, IN SEVEN PARTS.</h1>
以上sed命令保存在实例代码库中的top.sed文件中。用以下命令运行这些代码:
bash
sed -f top.sed rime2.txt
可以得到与前面的命令相同的输出。要将输出保存到文件,则可以把输出重定位到一个文件中,比如:
bash
sed -f top.sed rime2.txt > temp
除了会在屏幕上显示结果,重定位的那部分(>temp)还会将输出保存到文件temp中。
5.2、使用Perl添加标签
下面尝试用Perl完成同样的工作。先不解释,试一试这个:
bash
perl -ne 'print "<! DOCTYPE html>\
<html lang=\"en\">\
<head><title>Rime</title></head>\
<body>\
" if $. == 1;
s/^/<h1>/; s/$/<\/h1>/m; print; exit; ' rime2.txt

以下是该命令的工作过程。
-
变量 $.表示当前行,使用if语句测试。如果if语句返回true(真),则当前行就是第1行。
-
当Perl用if语句找到第1行时,它会打印文档类型(doctype)和几个HTML标签。和在sed中一样,这里也需要将引号转义。
-
第一个替换操作在行起始位置插入
标签,第二个替换操作在行尾处插入
标签。第二个替换操作结尾处的m表示使用多行修饰符。这就保证了该命令会识别第一行的结尾。如果没有m,则$会匹配该文件的结尾。
-
命令print打印替换结果。
-
命令exit则会立即结束Perl程序。否则,因为有-n选项,它会循环执行文件的每一行;但这里不需要。
以上命令要键入很多内容。我将这些Perl代码放入名为top.pl的文件中,也可以在代码库中找到。
bash
#! /usr/bin/perl -n
if ($ == 1) {
print "<! DOCTYPE html>\
<html lang=\"en\">\
<head>\
<title>The Rime of the Ancyent Mariner (1798)</title>\
</head>\
<body>\
";
s/^/<h1>/;
s/$/<\/h1>/m;
print;
exit;
}
使用合集运行这个文件:
js
perl top.pl rime2.txt

得到的结果与前面是一样的,只是形式上稍有不同。(就像在sed中一样,你可以用>将输出重定位到文件中。)