正则表达式——边界

边界

待匹配的文本:

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中一样,你可以用>将输出重定位到文件中。​)

相关推荐
营养充电站9 小时前
C++23 新特性在 CLion 中的实战体验
macos·docker·jupyter·正则表达式
吴声子夜歌20 小时前
正则表达式——字符组
正则表达式
天若有情6731 天前
自制浏览器端文本批量处理小工具,支持查找替换、内容删除、正则表达式,开箱即用
正则表达式
吴声子夜歌1 天前
正则表达式——简单的模式匹配
正则表达式
吴声子夜歌1 天前
正则表达式——前后查找
正则表达式
吴声子夜歌1 天前
正则表达式参考
正则表达式
吴声子夜歌2 天前
正则表达式——常见应用软件和编程语言中的正则表达式
正则表达式
吴声子夜歌2 天前
正则表达式——使用子表达式
正则表达式
吴声子夜歌3 天前
正则表达式——重复匹配
正则表达式