1.1 什么是正则表达式
1.1.1 定义
正则表达式是你所定义的模式模板(pattern template),Linux 工具可以用它来过滤文本。Linux 工具(比如 sed 编辑器或 gawk 程序)能够在处理数据时使用正则表达式对数据进行模式匹配。如 果数据匹配模式,它就会被接受并进一步处理;如果数据不匹配模式,它就会被滤掉
正则表达式模式利用通配符来描述数据流中的一个或多个字符。Linux 中有很多场景都可以 使用通配符来描述不确定的数据
*.txt 参数会让 ls 命令只列出名字以 txt 结尾的文件。文件名中以 txt 之前可以有任意多个字符(包括什么也没有)。ls 命令会读取目录中所有文件的信息,但只显示跟通配符匹配的文件的信息。 正则表达式通配符模式的工作原理与之类似。正则表达式模式含有文本或特殊字符,为 sed 编辑器和 gawk 程序定义了一个匹配数据时采用的模板。可以在正则表达式中使用不同的特殊字符来定义特定的数据过滤模式
1.1.2 正则表达式的类型
使用正则表达式大的问题在于有不止一种类型的正则表达式。Linux 中的不同应用程序可 能会用不同类型的正则表达式。这其中包括编程语言(Java、Perl 和 Python)、Linux 实用工具(比 如 sed 编辑器、gawk 程序和 grep 工具)以及主流应用(比如 MySQL 和 PostgreSQL 数据库服务器)。
正则表达式是通过正则表达式引擎(regular expression engine)实现的。正则表达式引擎是 一套底层软件,负责解释正则表达式模式并使用这些模式进行文本匹配。 在 Linux 中,有两种流行的正则表达式引擎: POSIX 基础正则表达式(basic regular expression,BRE)引擎
POSIX 扩展正则表达式(extended regular expression,ERE)引擎
大多数 Linux 工具都至少符合 POSIX BRE 引擎规范,能够识别该规范定义的所有模式符号。 遗憾的是,有些工具(比如 sed 编辑器)只符合了 BRE 引擎规范的子集。这是出于速度方面的考 虑导致的,因为 sed 编辑器希望能尽可能快地处理数据流中的文本。
POSIX BRE 引擎通常出现在依赖正则表达式进行文本过滤的编程语言中。它为常见模式提供 了高级模式符号和特殊符号,比如匹配数字、单词以及按字母排序的字符。gawk 程序用 ERE 引擎 来处理它的正则表达式模式
1.2 定义 BRE 模式
基本的 BRE 模式是匹配数据流中的文本字符
20.2.1 纯文本
第一个模式定义了一个单词 test。sed 编辑器和 gawk 程序脚本用它们各自的 print 命令打印出 匹配该正则表达式模式的所有行。由于 echo 语句在文本字符串中包含了单词 test,数据流文本能 够匹配所定义的正则表达式模式,因此 sed 编辑器显示了该行。
第二个模式也定义了一个单词,这次是 trial。因为 echo 语句文本字符串没包含该单词,所以 正则表达式模式没有匹配,因此 sed 编辑器和 gawk 程序都没打印该行。
你可能注意到了,正则表达式并不关心模式在数据流中的位置。它也不关心模式出现了多少 次。一旦正则表达式匹配了文本字符串中任意位置上的模式,它就会将该字符串传回 Linux 工具。
关键在于将正则表达式模式匹配到数据流文本上。重要的是记住正则表达式对匹配的模式非 常挑剔。第一条原则就是:正则表达式模式都区分大小写。这意味着它们只会匹配大小写也相符的模式
第一次尝试没能匹配成功,因为 this 在字符串中并不都是小写,而第二次尝试在模式中使 用大写字母,所以能正常工作。
在正则表达式中,你不用写出整个单词。只要定义的文本出现在数据流中,正则表达式就能够匹配
尽管数据流中的文本是 books,但数据中含有正则表达式 book,因此正则表达式模式跟数据 匹配。当然,反之正则表达式就不成立了
完整的正则表达式文本并未在数据流中出现,因此匹配失败,sed 编辑器不会显示任何文本。
你也不用局限于在正则表达式中只用单个文本单词,可以在正则表达式中使用空格和数字
在正则表达式中,空格和其他的字符并没有什么区别
如果你在正则表达式中定义了空格,那么它必须出现在数据流中。甚至可以创建匹配多个连续空格的正则表达式模式。
单词间有两个空格的行匹配正则表达式模式。这是用来查看文本文件中空格问题的好办法
1.2.2 特殊字符
在正则表达式模式中使用文本字符时,有些事情值得注意。在正则表达式中定义文本字符时 有一些特例。有些字符在正则表达式中有特别的含义。如果要在文本模式中使用这些字符,结果会超出你的意料。
正则表达式识别的特殊字符包括:
. * [] ^ $ {} \ + ? | ( )
如果要用某个特殊字符作为文本字符,就必须转义。在转义特殊字符时,你需要在它前面加 一个特殊字符来告诉正则表达式引擎应该将接下来的字符当作普通的文本字符。这个特殊字符就是反斜线(\)
如果要查找文本中的美元符,只要在它前面加个反斜线
由于反斜线是特殊字符,如果要在正则表达式模式中使用它,你必须对其转义,这样就产生 了两个反斜线
(sed 前面的两个 / / 表示字段匹配,后面的 \ 表示转义反斜线)
尽管正斜线不是正则表达式的特殊字符,但如果它出现在 sed 编辑器或 gawk 程序的正则表达式中,你就会得到一个错误
要使用正斜线,也需要进行转义
1.2.3 锚字符
默认情况下,当指定一个正则表达式模式时,只要模式出现在数据流中的任何地方,它就能匹配。有两个特殊字符可以用来将模式锁定在数据流中的行首或行尾
1. 锁定在行首
脱字符(^)定义从数据流中文本行的行首开始的模式。如果模式出现在行首之外的位置, 正则表达式模式则无法匹配
要用脱字符,就必须将它放在正则表达式中指定的模式前面
脱字符会在每个由换行符决定的新数据行的行首检查模式
只要模式出现在新行的行首,脱字符就能够发现它。 如果你将脱字符放到模式开头之外的其他位置,那么它就跟普通字符一样,不再是特殊字符了:
由于脱字符出现在正则表达式模式的尾部,sed 编辑器会将它当作普通字符来匹配
--------------------------------------------------------------------------------------------------------------------------------------------
说明 如果指定正则表达式模式时只用了脱字符,就不需要用反斜线来转义。但如果你在模式 中先指定了脱字符,随后还有其他一些文本,那么你必须在脱字符前用转义字符
--------------------------------------------------------------------------------------------------------------------------------------------
2. 锁定在行尾
跟在行首查找模式相反的就是在行尾查找。特殊字符美元符($)定义了行尾锚点。将这个特殊字符放在文本模式之后来指明数据行必须以该文本模式结尾
使用结尾文本模式的问题在于你必须要留意到底要查找什么
将行尾的单词 book 改成复数形式,就意味着它不再匹配正则表达式模式了,尽管 book 仍然在数据流中。要想匹配,文本模式必须是行的后一部分。
3. 组合锚点
在一些常见情况下,可以在同一行中将行首锚点和行尾锚点组合在一起使用。在第一种情况 中,假定你要查找只含有特定文本模式的数据行。
sed 编辑器忽略了那些不单单包含指定的文本的行。 第二种情况乍一看可能有些怪异,但极其有用。将两个锚点直接组合在一起,之间不加任何文本,这样过滤出数据流中的空白行。
考虑下面这个例子
定义的正则表达式模式会查找行首和行尾之间什么都没有的那些行。由于空白行在两个换行 符之间没有文本,刚好匹配了正则表达式模式。sed 编辑器用删除命令 d 来删除匹配该正则表达式 模式的行,因此删除了文本中的所有空白行。这是从文档中删除空白行的有效方法
1.2.4 点号字符
特殊字符点号用来匹配除换行符之外的任意单个字符。它必须匹配一个字符,如果在点号字符的位置没有字符,那么模式就不成立
你应该能够明白为什么第一行无法匹配,而第二行和第三行就可以。第四行有点复杂。注意, 我们匹配了 at,但在 at 前面并没有任何字符来匹配点号字符。其实是有的!在正则表达式中, 空格也是字符,因此 at 前面的空格刚好匹配了该模式。第五行证明了这点,将 at 放在行首就不 会匹配该模式了
1.2.5 字符组 ( 字符组中字符不区分先后顺序)
点号特殊字符在匹配某个字符位置上的任意字符时很有用。但如果你想要限定待匹配的具体字符在正则表达式中,这称为字符组(character class)。
可以定义用来匹配文本模式中某个位置的一组字符。如果字符组中的某个字符出现在了数据 流中,那它就匹配了该模式。
使用方括号来定义一个字符组。方括号中包含所有你希望出现在该字符组中的字符。然后你 可以在模式中使用整个组,就跟使用其他通配符一样。这需要一点时间来适应,但一旦你适应了, 效果可是令人惊叹的
这里用到的数据文件和点号特殊字符例子中的一样,但得到的结果却不一样。这次我们成功 滤掉了只包含单词 at 的行。匹配这个模式的单词只有 cat 和 hat。还要注意以 at 开头的行也没有匹配。字符组中必须有个字符来匹配相应的位置。 在不太确定某个字符的大小写时,字符组会非常有用
可以在单个表达式中用多个字符组
正则表达式使用了 3 个字符组来涵盖了 3 个字符位置含有大小写的情况。
字符组不必只含有字母,也可以在其中使用数字
这个正则表达式模式匹配了任意含有数字 0、1、2 或 3 的行。含有其他数字以及不含有数字的行都会被忽略掉
可以将字符组组合在一起,以检查数字是否具备正确的格式,比如电话号码和邮编。但当你尝试匹配某种特定格式时,必须小心。这里有个匹配手机号码出错的例子
这个结果出乎意料。它成功过滤掉了不可能是手机号码的那些过短的数字,因为后一个字符组没有字符可匹配。但它也通过了那个 20 位数,尽管我们只定义了 11 个字符组。 记住,正则表达式模式可见于数据流中文本的任何位置。经常有匹配模式的字符之外的其他字符。如果要确保只匹配 11 位数,就必须将匹配的字符和其他字符分开,要么用空格,要么像这 个例子中这样,指明它们就在行首和行尾。
( 使用锚字符中的脱字符和美元符来实现 )
这个结果就好多了
字符组的一个极其常见的用法是解析拼错的单词,比如用户表单输入的数据。你可以创建正则表达式来接受数据中常见的拼写错误
本例中的两个 sed 打印命令利用正则表达式字符组来帮助找到文本中是否拼错的单词 memory 和 Zhifubao。
1.2.6 排除性字符组
在正则表达式模式中,也可以反转字符组的作用。可以寻找组中没有的字符,而不是去寻找组中含有的字符。要这么做的话,只要在字符组的开头加个脱字符
通过排除型字符组,正则表达式模式会匹配 c 或 h 之外的任何字符以及文本模式以及后边的 at。由于空格字符属于这个范围,它通过了模式匹配。但即使是排除,字符组仍然必须匹配一个字符,所以以 at 开头的行仍然未能匹配模式
1.2.7 区间
之前演示手机号码的例子的时候,必须在每个字符组中列出所有可能的数字, 这实在有点麻烦。好在有一种便捷的方法可以让人免受这番劳苦。可以用单破折线符号在字符组中表示字符区间。只需要指定区间的第一个字符、单破折线以及区间的后一个字符就行了。根 据 Linux 系统采用的字符集,正则表达式会包括此区间内的任意字符。 现在你可以通过指定数字区间来简化手机号码的例子。
这样可是节省了不少的键盘输入!每个字符组都会匹配 0~9 的任意数字。如果字母出现在数据中的任何位置,这个模式都将不成立
同样的方法也适用于字母
新的模式 [c-h]at 匹配了首字母在字母 c 和字母 h 之间的单词。这种情况下,只含有单词 at 的行将无法匹配该模式。
还可以在单个字符组指定多个不连续的区间
该字符组允许区间 a~c、h~m 中的字母出现在 at 文本前,但不允许出现 d~g 的字母
该模式不匹配 fat 文本,因为它没在指定的区间
1.2.8 特殊的字符组
除了定义自己的字符组外,BRE 还包含了一些特殊的字符组,可用来匹配特定类型的字符。
可以在正则表达式模式中将特殊字符组像普通字符组一样使用
使用特殊字符组可以很方便地定义区间。可以用 [[:digit:]] 来代替区间[0-9]
1.2.9 星号
在字符后面放置星号表明该字符必须在匹配模式的文本中出现 0 次或多次
这个模式符号广泛用于处理有常见拼写错误或在不同语言中有拼写变化的单词。举个例子, 如果需要写个可能用在美式或英式英语中的脚本,可以这么写:
模式中的 u * 表明字母 u 可能出现或不出现在匹配模式的文本中。类似地,如果你知道一个单 词经常被拼错,你可以用星号来允许这种错误
在可能出现的额外字母后面放个星号将允许接受拼错的单词。
另一个方便的特性是将点号特殊字符和星号特殊字符组合起来。这个组合能够匹配任意数量的任意字符。它通常用在数据流中两个可能相邻或不相邻的文本字符串之间
可以使用这个模式轻松查找可能出现在数据流中文本行内任意位置的多个单词。 星号还能用在字符组上。它允许指定可能在文本中出现多次的字符组或字符区间
只要 a 和 e 字符以任何组合形式出现在 b 和 t 字符之间(就算完全不出现也行),模式就能够匹配。如果出现了字符组之外的字符,该模式匹配就会不成立
1.3 扩展正则表达式
POSIX ERE 模式包括了一些可供 Linux 应用和工具使用的额外符号。gawk 程序能够识别 ERE 模式,但 sed 编辑器不能。
-------------------------------------------------------------------------------------------------------------------------------------------
警告 记住,sed 编辑器和 gawk 程序的正则表达式引擎之间是有区别的。gawk 程序可以使用大多 数扩展正则表达式模式符号,并且能提供一些额外过滤功能,而这些功能都是 sed 编辑器 所不具备的。但正因为如此,gawk 程序在处理数据流时通常才比较慢
-------------------------------------------------------------------------------------------------------------------------------------------
1.3.1 问号
问号类似于星号,不过有点细微的不同。问号表明前面的字符可以出现 0 次或 1 次,但只限于此。它不会匹配多次出现的字符
如果字符 e 并未在文本中出现,或者它只在文本中出现了 1 次,那么模式会匹配。
与星号一样,你可以将问号和字符组一起使用
如果字符组中的字符出现了 0 次或 1 次,模式匹配就成立。但如果两个字符都出现了,或者其中一个字符出现了 2 次,模式匹配就不成立
1.3.2 加号
加号是类似于星号的另一个模式符号,但跟问号也有不同。加号表明前面的字符可以出现 1 次或多次,但必须至少出现 1 次。如果该字符没有出现,那么模式就不会匹配
如果字符 e 没有出现,模式匹配就不成立。加号同样适用于字符组,与星号和问号的使用方式相同
这次如果字符组中定义的任一字符出现了,文本就会匹配指定的模式
1.3.3 使用花括号
** ERE 中的花括号允许你为可重复的正则表达式指定一个上限。这通常称为间隔(interval)。 可以用两种格式来指定区间。**
** m:正则表达式准确出现 m 次。**
** m, n:正则表达式至少出现 m 次,至多 n 次。**
** 这个特性可以精确调整字符或字符集在模式中具体出现的次数**
---------------------------------------------------------------------------------------------------------------------------------------
警告 默认情况下,gawk 程序不会识别正则表达式间隔。必须指定 gawk 程序的 --re- interval 命令行选项才能识别正则表达式间隔。
---------------------------------------------------------------------------------------------------------------------------------------
这里有个使用简单的单值间隔的例子
通过指定间隔为 1,限定了该字符在匹配模式的字符串中出现的次数。如果该字符出现多次, 模式匹配就不成立。
很多时候,同时指定下限和上限也很方便。
在这个例子中,字符 e 可以出现 1 次或 2 次,这样模式就能匹配;否则,模式无法匹配。
间隔模式匹配同样适用于字符组
如果字母 a 或 e 在文本模式中只出现了 1~2 次,则正则表达式模式匹配;否则,模式匹配失败
1.3.4 管道符号
管道符号允许你在检查数据流时,用逻辑 or 方式指定正则表达式引擎要用的两个或多个模 式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本 匹配失败。
使用管道符号的格式如下:
expr1|expr2|...
这个例子会在数据流中查找正则表达式 cat 或 dog。正则表达式和管道符号之间不能有空格, 否则它们也会被认为是正则表达式模式的一部分。
管道符号两侧的正则表达式可以采用任何正则表达式模式(包括字符组)来定义文本。
1.3.5 表达式分组
正则表达式模式也可以用圆括号进行分组。当你将正则表达式模式分组时,该组会被视为一 个标准字符。可以像对普通字符一样给该组使用特殊字符。
举个例子:
结尾的 urday 分组以及问号,使得模式能够匹配完整的 Saturday 或缩写 Sat。
将分组和管道符号一起使用来创建可能的模式匹配组是很常见的做法
1.4 正则表达式实战
1.4.1 目录文件计数
先看一个 shell 脚本,它会对 PATH 环境变量中定义的目录里的可执行文件进行计数。要 这么做的话,首先你得将 PATH 变量解析成单独的目录名.
根据 Linux 系统上应用程序所处的位置,PATH 环境变量会有所不同。关键是要意识到 PATH 中 的每个路径由冒号分隔。要获取可在脚本中使用的目录列表,就必须用空格来替换冒号。现在你会发现 sed 编辑器用一条简单表达式就能完成替换工作
分离出目录之后,你就可以使用标准 for 语句中来遍历每个目录
** mypath=$(echo $PATH | sed 's/:/ /g')**
** for directory in $mypath**
** do**
** ...**
** done**
** 一旦获得了单个目录,就可以用 ls 命令来列出每个目录中的文件,并用另一个 for 语句来遍 历每个文件,为文件计数器增值**
实现目录文件计数的脚本如下:
1.4.2 验证电话号码
前面的例子演示了在处理数据时,如何将简单的正则表达式和 sed 配合使用来替换数据流中的字符。正则表达式通常用于验证数据,确保脚本中数据格式的正确性
一个常见的数据验证应用就是检查电话号码。数据输入表单通常会要求填入电话号码,而用 户输入格式错误的电话号码是常有的事。在美国,电话号码有几种常见的形式:(仅举出例子加深知识点记忆)
(123)456-7890
(123) 456-7890
123-456-7890
123.456.7890
这样用户在表单中输入的电话号码就有 4 种可能。正则表达式必须足够强大,才能处理每一 种情况
在构建正则表达式时,最好从左手边开始,然后构建用来匹配可能遇到的字符的模式。在这个例子中,电话号码中可能有也可能没有左圆括号。这可以用如下模式来匹配:
^(?
脱字符用来表明数据的开始。由于左圆括号是个特殊字符,因此必须将它转义成普通字符。 问号表明左圆括号可能出现,也可能不出现
紧接着就是 3 位区号。在美国,区号以数字 2 开始(没有以数字 0 或 1 开始的区号),大可到 9。 要匹配区号,可以用如下模式
[2-9][0-9]{2}
** ----------------------------------------------------------------------------------------------------------------------------------**
这里 [2-9] 是说第一位数字范围在 2-9 之间,[0-9]是说范围在 0-9 之间,紧跟着的 {2} 是说 [0-9] 的数字出现两次,也就是 2 位 0-9 的数字。。只有当([2-9][0-9]{2})用小括号(),在一个分组下的时候才说明产生的 {2} 与前面的[2-9] 有关系
** ----------------------------------------------------------------------------------------------------------------------------------**
这要求第一个字符是 2~9 的数字,后跟任意两位数字。在区号后面,收尾的右圆括号可能存 在,也可能不存在
)?
在区号后,存在如下可能:有一个空格,没有空格,有一条单破折线或一个点。你可以对它 们使用管道符号,并用圆括号进行分组
(| |-|.)
第一个管道符号紧跟在左圆括号后,用来匹配没有空格的情形。你必须将点字符转义,否则 它会被解释成可匹配任意字符
紧接着是 3 位电话交换机号码。这里没什么需要特别注意的
[0-9]{3}
在电话交换机号码之后,你必须匹配一个空格、一条单破折线或一个点(这次不用考虑匹配 没有空格的情况,因为在电话交换机号码和其余号码间必须有至少一个空格)
(|-|.)
最后,必须在字符串尾部匹配 4 位本地电话分机号
[0-9]{4}$
完整的模式如下
^?[2−9][0−9]2?[2−9][0−9]2?[2-9][0-9]{2}?(| |-|.)[0-9]{3}( |-|.)[0-9]{4}$
你可以在 gawk 程序中用这个正则表达式模式来过滤掉不符合格式的电话号码。现在你只需要 在 gawk 程序中创建一个使用该正则表达式的简单脚本,然后用这个脚本来过滤你的电话薄。记住, 在 gawk 程序中使用正则表达式间隔时,必须使用 --re-interval 命令行选项,否则就没法得到正确的结果
脚本如下:
虽然从上面的清单中看不出来,但是 shell 脚本中的 gawk 命令是单独在一行上的。可以将电话号码重定向到脚本来处理
或者也可以将含有电话号码的整个文件重定向到脚本来过滤掉无效的号码
只有匹配该正则表达式模式的有效电话号码才会出现
1.4.3 解析邮件地址
如今这个时代,电子邮件地址已经成为一种重要的通信方式。验证邮件地址成为脚本程序员 的一个不小的挑战,因为邮件地址的形式实在是千奇百怪。邮件地址的基本格式为:
username@hostnameusername 值可用字母数字字符以及以下特殊字符:
点号
单破折线
加号
下划线
在有效的邮件用户名中,这些字符可能以任意组合形式出现。邮件地址的 hostname 部分由 一个或多个域名和一个服务器名组成。服务器名和域名也必须遵照严格的命名规则,只允许字母 数字字符以及以下特殊字符:
点号
下划线
服务器名和域名都用点分隔,先指定服务器名,紧接着指定子域名,后是后面不带点号的顶级域名。
顶级域名的数量在过去十分有限,正则表达式模式编写者会尝试将它们都加到验证模式中。 然而遗憾的是,随着互联网的发展,可用的顶级域名也增多了。这种方法已经不再可行。
从左侧开始构建这个正则表达式模式。我们知道,用户名中可以有多个有效字符。这个相当容易
^([a-zA-Z0-9_-.+]+)@
这个分组指定了用户名中允许的字符,加号表明必须有至少一个字符。下一个字符很明显是 @,没什么意外的。
hostname 模式使用同样的方法来匹配服务器名和子域名
([a-zA-Z0-9_-.]+)
这个模式可以匹配文本
server
server.subdomain
server.subdomain.subdomain
对于顶级域名,有一些特殊的规则。顶级域名只能是字母字符,必须不少于二个字符(国家 或地区代码中使用),并且长度上不得超过五个字符。下面就是顶级域名用的正则表达式模式
.([a-zA-Z]{2,5})$
将整个模式放在一起会生成如下模式。:
^([a-zA-Z0-9_-.+]+)@([a-zA-Z0-9_-.]+).([a-zA-Z]{2,5})$
这个模式会从数据列表中过滤掉那些格式不正确的邮件地址。现在可以创建脚本来实现这个 正则表达式了
小结:
如果你在 shell 脚本中处理数据文件,就必须熟悉正则表达式。正则表达式在 Linux 实用工具、 编程语言以及采用了正则表达式引擎的应用程序中均有实现。在 Linux 中有一些不同的正则表达 式引擎。流行的两种是 POSIX 基础正则表达式(BRE)引擎和 POSIX 扩展正则表达式(ERE) 引擎。sed 编辑器基本符合 BRE 引擎,而 gawk 程序则使用了 ERE 引擎中的大多数特性。
正则表达式定义了用来过滤数据流中文本的模式模板。模式由标准文本字符和特殊字符的组 成。正则表达式引擎用特殊字符来匹配一系列单个或多个字符,这类似于其他应用程序中通配符 的工作方式。
通过结合字符和特殊字符,你能够定义出匹配大多数数据类型的模式。然后你可以用 sed 编 辑器或 gawk 程序从大型数据流中过滤特定数据,或者验证从其他数据输入应用程序收到的数据。
下一章将会更深入地使用 sed 编辑器来进行高级文本处理。sed 编辑器中的许多高级功能让它在处理大型数据流和过滤数据时非常有用