简单的模式匹配
被匹配的文本如下:
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.
1 It is an ancyent Marinere,
2 And he stoppeth one of three:
3 "By thy long grey beard and thy glittering eye
4 "Now wherefore stoppest me?
1、匹配字符串字面值
正则表达式最为直接和明显的功能就是用一个或多个字符字面值来匹配字符串。
匹配字符串字面值的方法就是使用普通的字符。这听起来是否有些熟悉呢?这就和你在Word等字处理程序中使用查找或者在搜索引擎中输入关键字类似。当你以逐个字符对应的方式查找文本字符串的时候,就是在用字符串字面值查找。
举个例子,如果你要匹配以上诗文中开头部分的单词Ship,只需在RegExr上方的文本框中键入Ship,该单词就会在下方的文本框中标亮(首字母要大写)。

2、匹配数字
在RegExr中左上方的文本框中,输入以下字符组简写式来匹配数字:
js
\d

因为默认勾选了global(全局匹配)复选框,这将会匹配下方文本区域中所有的阿拉伯数字。
现在用字符组替代\d来匹配相同的内容。在RegExr的上方文本框中输入以下范围的数字:
js
[0-9]

虽然语法不一样,但\d和0-9的效果是一样的。
字符组[0-9]表示范围,这意味着它会匹配0至9范围内的数字。你也可以列出0至9范围内的所有数字来进行匹配:
js
[0123456789]

如果只想匹配0和1两个数字,可以使用这个字符组:
js
[01]

请在RegExr中尝试一下12并看看结果。使用字符组可精确匹配字符。数字的字符组简写式\d更为简短,但却没有字符组强大、灵活。在无法使用\d时(不是所有情况下都支持这种方式),或者想匹配特定数字时,就需要使用字符组;合适的时候可以使用\d,因为它更简短。
3、匹配非数字字符
通常可以将简写式取反,取反的结果就是排除。比如,要匹配非数字字符,可使用包含以下大写字母D的简写式:
js
\D

请在RegExr中试一试。大写字母D取代小写字母d,就会匹配非数字字符(如图所示)。
该简写式与以下字符组取反的作用相同(字符组取反的意思其实就是"不匹配这些"或"匹配除这些以外的内容"):
js
[^0-9]
下面这个表达式作用也一样:
js
[^\d]
4、匹配单词和非单词字符
在RegExr中,将\D替换为:
js
\w

这个简写式将匹配所有的单词字符(前提是勾选global选项)。\D与\w的区别是\D会匹配空格、标点符号(引号、连字符、反斜杠、方括号)等字符,而\w却不会,它只匹配字母、数字和下划线。
在英语环境中,与\w匹配相同内容的字符组为:
js
[_a-zA-Z0-9]

现在用大写字母W匹配非单词字符:
js
\W

在本示例中,这个简写式匹配空格、标点以及其他非字母、非数字字符。使用以下字符组也可以匹配相同的内容:
js
[^_a-zA-Z0-9]
字符组允许你匹配更多类型的字符,但有时你不想而且也没有必要键入所有字符。这也就是"按键次数最少则胜"的原则。但有时你确实需要将所有的字符键入才能得到准确的结果。反正你自己决定。
轻松一下,在RegExr中试一下:
js
[^\w]

以及
js
[^\W]

下表提供了更多的字符简写式。不过并不是所有的正则表达式处理器都能识别这些简写式。

5、匹配空白符
可以用以下简写式匹配空白符:
js
\s

请在RegExr试一试并看看高亮的部分。以下字符组与\s匹配的内容相同:
js
[ \t\n\r]

也就是说,它会匹配:
- 空格
- 制表符(\t)
- 换行符(\n)
- 回车符(\r)
可想而知,\s也有对应的大写形式。要匹配非空白字符,则使用:
js
\S

这个简写式匹配除空白符之外的所有符号。它匹配字符组:
js
[^ \t\n\r]
或者是:
js
[^\s]
除了\s匹配的字符之外,还有其他不太常见的空白字符。下表列出了匹配常见和不太常见的空白字符的简写式。

6、再谈匹配任意字符
用正则表达式匹配任意字符的一种方法就是使用点号(U+002E)。点号可以匹配除行结束符之外的所有字符,个别情况除外。
在RegExr中,去掉对global复选框的勾选。这样,任何正则表达式都会匹配目标文本中第一个匹配项。
在RegExr上方的文本框中键入单个点号来匹配任意字符。
如图所示,点号匹配了目标文本中的第一个字符T。

要匹配THE RIME整个短语,则可使用八个点号:
js
........

但这种方法太麻烦,所以推荐用量词:
js
.{8}
这个表达式就能匹配前两个单词以及它们之间的空格,但只是粗略地匹配。勾选global旁的复选框,看看这个表达式还有什么作用,你就知道我所说的粗略是什么意思了。它匹配了连续多组的八个字符,头尾相连,只有目标文本的最后几个字符除外。
下面我们再试试匹配单词的边界和字母的开始和结束位置。在RegExr上方文本框中键入以下内容,可以看到细微的差异:
js
\bA.{5}T\b

这个表达式有更强的特指性(请记住特指性,specificity,这个概念很重要),它匹配单词ANCYENT,也就是ancient的旧式拼写形式。这是如何做到的呢?
- 简写式 \b匹配单词边界,不消耗任何字符;
- 字符A和T限定了字符序列的首尾字母;
- .{5} 匹配任意五个字符;
- 简写式 \b匹配单词的另一个边界。
这个正则表达式实际上可以匹配ANCYENT和ANCIENT。
现在再试一下这个简写式:
js
\b\w{7}\b

最后再试试匹配零个或多个字符:
js
.*
它就相当于:
js
[^\n]
或
js
[^\n\r]

类似地,点号也可以与表示"一个或多个"的量词(+)连用:
js
.+
请在RegExr中尝试这些表达式,它们都会匹配第一行内容(在取消勾选global复选框的情况下)。原因是点号通常不会匹配折行符,例如换行符(U+000A)或回车符(U+000D)。勾选dotall旁边的复选框,然后.*和.+就会匹配下方文本框中的全部文本。(dotall表示点号匹配包括换行符在内的所有字符。)
这就涉及量词的贪心特性了。所谓"贪心"(greedy),指量词会匹配所有能匹配的字符。别急,第7章会详细介
7、给文本加标签
怎么让"The Rime of the Ancient Mariner"的内容以网页而不是纯文本形式显示呢?换句话说,怎么使用正则表达式而不是手写方式为它们添加HTML5标签呢?
在之后的几章中,我会陆续展示相应的方法,本章先从简单情况的开始。
点击RegExr中的Replace(替换)标签,选中multiline(多行)选项,然后在第一个文本框中键入:
js
(^T.*$)

这个表达式会从文件的开始匹配诗文的第一行,然后使用括号将文本捕获到一个分组中。在第二个文本框中键入:
js
<h1>$1<\/h1>

这个替换表达式将1捕获的内容嵌套在了h1标签中。可以在底部的文本框中看到结果。1是一个Perl风格的后向引用。此外,在包括Perl在内的多数实现程序中,还可以使用\1表示后向引用,但是RegExr只支持1、2、$3这样的方式。
7.1、用sed为文本加标签
在命令行中使用sed也可以为文本添加标签。sed是Unix流编辑器,它支持用正则表达式转换文本。sed最初在20世纪70年代早期由Lee McMahon于贝尔实验室开发。如果用户使用的是Mac或者Linux,那就已经有sed了。
请在shell提示符(比如Mac中的Terminal终端窗口)中测试以下内容:
js
echo Hello | sed s/Hello/Goodbye/

运行的过程应该如下:
- echo命令将在标准输出设备(通常是屏幕)中打印单词Hello,竖线符(|)将打印内容通过管道传到之后的sed命令;
- 管道将echo的输出转为sed的输入;
- sed的s命令将单词Hello变为Goodbye,而Goodbye就显示在屏幕上了。
现在试着在命令或shell提示符中键入:
bash
sed -n 's/^/<h1>/; s/$/<\/h1>/p; q' rime.txt

以下是正则表达式处理器的工作过程解析。
- 首先调用sed程序。
- sed默认的操作是直接复制每行输入并输出,-n选项覆盖了该默认操作。之所要覆盖默认操作,是因为我们只想让正则表达式影响第1行。
s/ ^/<h1>/在行的开头(^)添加<h1>标签。- 分号(;)用于分隔命令。
s/$/<\/h1>/在行的结尾($)添加</h1>标签。- 命令p会打印受影响的那一行(第1行)。与-n不同,后者会打印所有行。
- 最后命令q会结束程序,这样sed程序就只会处理第1行。
- 所有的操作都是针对rime.txt文件执行的。
这行命令还有另一种写法,即用-e选项分别引导每个命令。我当然更喜欢使用带分号的写法,因为那种写法更简短。
bash
sed -ne 's/^/<h1>/' -e 's/$/<\/h1>/p' -e 'q' rime.txt

可以将这些命令写到文件里,比如这里所示的文件h1.sed(该文件就在之前所说的代码库里):
txt
#! /usr/bin/sed
s/^/<h1>/
s/$/<\/h1>/
q
若要运行该文件,请在与rime.txt同一个路径或文件夹里运行如下命令:
bash
sed -f h1.sed rime.txt

7.2、用Perl为文本加标签
最后,我将展示如何用Perl来做类似的事。Perl是由LarryWall于1987年创立的一种通用程序设计语言。它以对正则表达式的强大支持和文本处理能力而闻名。
在命令提示符中键入
bash
perl -v
然后回车,看看你的系统中是否已经安装了Perl。该命令会返回系统中Perl的版本信息或者返回错误。
在命令提示符中键入:
bash
perl -ne 'if ($. == 1) { s/^/<h1>/; s/$/<\/h1>/m; print; }' rime.txt

就可以得到和sed示例中一样的输出结果。
以下是这个Perl命令的执行过程解析。
- perl调用了Perl程序。
- -n选项输出全部输入内容(rime.txt文件)。
- -e选项允许在命令行(而不用在文件)中提交程序代码。
- if语句检查是否在第一行。在Perl中$.是个特殊的变量,它匹配当前行。
- 第一个替换命令s先找到第一行的开头(^)然后插入<h1>标签。
- 第二个替换命令s搜寻行结尾($)再插入</h1>标签。
- 替换命令最后的m(多行)修饰符,表示将本行单独处理;这样,$就只匹配第一行的结尾而不会匹配整个文本的结尾。
- 最后在标准输出设备(屏幕)中打印出结果。
- 所有这些操作都是针对rime.txt文件的。
同样可以将这些命令放入程序文件中,比如示例代码库中的h1.pl文件。
perl
#! /usr/bin/perl -n
if ($. == 1) {
s/^/<h1>/;
s/$/<\/h1>/m;
print;
}
然后在rime.txt的同一个目录下,执行如下命令:
bash
perl h1.pl rime.txt
