《Linux命令行与shell脚本编程大全》,4 E -- Chapter 20
一、 什么是正则表达式
1. 定义
- 正则表达式是你所定义的模式模板(pattern template),Linux工具可以用它来过滤文本。Linux工具(比如sed编辑器或gawk程序)能够在处理数据时使用正则表达式对数据进行模式匹配。如果数据匹配模式,它就会被接受并进一步处理;如果数据不匹配模式,它就会被滤掉。图20-1描述了这个过程。
- 正则表达式模式利用通配符来描述数据流中的一个或多个字符。Linux中有很多场景都可以使用通配符来描述不确定的数据。你已经看到过在Linux的 ls 命令中使用通配符列出文件和目录的例子(参见第3章)。星号通配符允许你只列出满足特定条件的文件,例如:
$ ls -al da*
-rw-r--r-- 1 rich rich 45 Nov 26 12:42 data
-rw-r--r-- 1 rich rich 25 Dec 4 12:40 data.tst
-rw-r--r-- 1 rich rich 180 Nov 26 12:42 data1
-rw-r--r-- 1 rich rich 45 Nov 26 12:44 data2
-rw-r--r-- 1 rich rich 73 Nov 27 12:31 data3
-rw-r--r-- 1 rich rich 79 Nov 28 14:01 data4
-rw-r--r-- 1 rich rich 187 Dec 4 09:45 datatest
-
da*
参数会让ls
命令只列出名字以da
开头的文件。文件名中da之后可以有任意多个字 符(包括什么也没有),ls
命令会读取目录中所有文件的信息,但只显示跟通配符匹配的文件的信息。正则表达式通配符模式的工作原理与之类似。正则表达式模式含有文本或特殊字符,为sed编辑器和gawk程序定义了一个匹配数据时采用的模板。可以在正则表达式中使用不同的特殊字符来定义特定的数据过滤模式。
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)引擎
- 由于实现正则表达式的方法太多,很难用一个简洁的描述来涵盖所有可能的正则表达式。
二、 定义 BRE 模式
- 最基本的BRE模式是匹配数据流中的文本字符。本节将会演示如何在正则表达式中定义文本
以及会得到什么样的结果。
1. 纯文本
- 第18章演示了如何在sed编辑器和gawk程序中用标准文本字符串来过滤数据。通过下面的例
子来复习一下。
$ echo "This is a test" | sed -n '/test/p'
This is a test
$·· echo "This is a test" | sed -n '/trial/p'
$ echo "This is a test" | gawk '/test/{print $0}'
This is a test
$ echo "This is a test" | gawk '/trial/{print $0}
- 第一个模式定义了一个单词test。sed编辑器和gawk程序脚本用它们各自的 print 命令打印出匹配该正则表达式模式的所有行。由于 echo 语句在文本字符串中包含了单词test,数据流文本能够匹配所定义的正则表达式模式,因此sed编辑器显示了该行。
- 第二个模式也定义了一个单词,这次是trial。因为 echo 语句文本字符串没包含该单词,所以正则表达式模式没有匹配,因此sed编辑器和gawk程序都没打印该行。
- 正则表达式并不关心模式在数据流中的位置。它也不关心模式出现了多少次。一旦正则表达式匹配了文本字符串中任意位置上的模式,它就会将该字符串传回Linux工具。关键在于将正则表达式模式匹配到数据流文本上。
- 重要的是记住正则表达式对匹配的模式非常挑剔。第一条原则就是:正则表达式模式都区分大小写。这意味着它们只会匹配大小写也相符的模式。
$ echo "This is a test" | sed -n '/this/p'
$ echo "This is a test" | sed -n '/This/p'
This is a test
- 在正则表达式中,你不用写出整个单词。只要定义的文本出现在数据流中,正则表达式就能够匹配。
$ echo "The books are expensive" | sed -n '/book/p'
The books are expensive
-
不用局限于在正则表达式中只用单个文本单词,可以在正则表达式中使用空格和数字
单词间有两个空格的行匹配正则表达式模式。这是用来查看文本文件中空格问题的好办法。
$ cat data1
This is a normal line of text.
This is a line with too many spaces.
$ sed -n '/ /p' data1
This is a line with too many spaces.
2. 特殊字符
在正则表达式中定义文本字符时,有一些特例。有些字符在正则表达式中有特别的含义。如果要在文本模式中使用这些字符,结果会超出你的意料。
-
正则表达式识别的特殊字符包括:
·.*[]^${}\+?|(
不能在文本模式中单独使用这些字符,如果要用某个特殊字符作为文本字符,就必须转义符号反斜线(\)。转义字符来告诉正则表达式引擎应该将接下来的字符当作普通的文本字符。
如果要查找文本中的美元符,只要在它前面加个反斜线。
$ cat data2
The cost is $4.00
$ sed -n '/\$/p' data2
The cost is $4.00
- 由于反斜线是特殊字符,如果要在正则表达式模式中使用它,你必须对其转义,这样就产生
了两个反斜线。
$ echo "\ is a special character" | sed -n '/\\\/p'
\ is a special character
- 尽管正斜线不是正则表达式的特殊字符,但如果它出现在sed编辑器或gawk程序的正则表达式中,你就会得到一个错误。要使用正斜线,也需要进行转义。
$ echo "3 / 2" | sed -n '///p'
sed: -e expression #1, char 3: unknown command: `/'
$ echo "3 / 2" | sed -n '/\//p'
3 / 2
3. 锚字符
- 默认情况下,当指定一个正则表达式模式时,只要模式出现在数据流中的任何地方,它就能匹配。有两个特殊字符可以用来将模式锁定在数据流中的行首或行尾。
3.1 锁定在行首
- 脱字符(^)定义从数据流中文本行的行首开始的模式。如果模式出现在行首之外的位置,正则表达式模式则无法匹配。
$ echo "The book store" | sed -n '/^ book/p'
$ echo "Books are great" | sed -n '/^ Book/p'
Books are great
- 脱字符会在每个由换行符决定的新数据行的行首检查模式。
$ cat data3
This is a test line.
this is another test line.
A line that tests this feature.
Yet more testing of this
$ sed -n '/^ this/p' data3
this is another test line.
- 如果你将脱字符放到模式开头之外的其他位置,那么它就跟普通字符一样,不再是特殊字符了:
$ echo "This ^ is a test" | sed -n '/s ^ /p'
This ^ is a test
说明 :如果指定正则表达式模式时只用了脱字符,就不需要用反斜线来转义。但如果你在模式中先指定了脱字符,随后还有其他一些文本,那么你必须在脱字符前用转义字符。
3.2 锁定在行尾
- 跟在行首查找模式相反的就是在行尾查找。特殊字符美元符($)定义了行尾锚点。将这个特殊字符放在文本模式之后来指明数据行必须以该文本模式结尾。
echo "This is a good book" | sed -n '/book$/p'
This is a good book
echo "This book is good" | sed -n '/book$/p'
3.3 组合锚点
一些常见情况下,可以在同一行中将行首锚点和行尾锚点组合在一起使用。
-
^模式$
,查找只含有特定文本模式的数据行。
cat data4
this is a test of using both anchors
I said this is a test
this is a test
I'm sure this is a test.
$ sed -n '/^this is a test$/p' data4
this is a test
-
^$
,将两个锚点直接组合在一起,之间不加任何文本,这样过滤出数据流中的空白行
$ cat data5
This is one test line.
This is one test line.
This is another test line.
sed '/^ $/d' data5
This is one test line.
This is another test line.
定义的正则表达式模式会查找行首和行尾之间什么都没有的那些行。由于空白行在两个换行符之间没有文本,刚好匹配了正则表达式模式。sed编辑器用删除命令 d 来删除匹配该正则表达式模式的行,因此删除了文本中的所有空白行。这是从文档中删除空白行的有效方法。
3.4 点号字符
- 特殊字符点号用来匹配除换行符之外的任意单个字符。它必须匹配一个字符,如果在点号字符的位置没有字符,那么模式就不成立。
$ cat data6
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
$ sed -n '/.at/p' data6
The cat is sleeping.
That is a very nice hat.
This test is at line four.
行有点复杂。注意,我们匹配了 at ,但在 at 前面并没有任何字符来匹配点号字符。其实是有的!在正则表达式中,空格也是字符,因此 at 前面的空格刚好匹配了该模式。第五行证明了这点,将 at 放在行首就不会匹配该模式了。
3.4 字符组
- 点号特殊字符在匹配某个字符位置上的任意字符时很有用。但如果你想要限定待匹配的具体字符呢?在正则表达式中,这称为字符组(character class)。可以定义用来匹配文本模式中某个位置的一组字符。如果字符组中的任意一个字符出现在了数据流中,那它就匹配了该模式。
- 使用方括号来定义一个字符组。方括号中包含所有你希望出现在该字符组中的字符。然后你可以在模式中使用整个组,就跟使用其他通配符一样。这需要一点时间来适应,但一旦你适应了,效果可是令人惊叹的。
下面是个创建字符组的例子。
$ cat data6
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
$ sed -n '/[ch]at/p' data6
The cat is sleeping.
That is a very nice hat.
首先筛选出了含有at的行,但不包含at ten o'clock we'll go home.
这一行,因为这一行的at前面没有字符,而该模式规定除了匹配at字符之外,还要匹配s字符或h字符,因此at ten o'clock we'll go home.
淘汰,The cat is sleeping.
和That is a very nice hat.
通过。
- 在不太确定某个字符的大小写时,字符组会非常有用。
$ echo "Yes" | sed -n '/[Yy]es/p'
Yes
$ echo "yes" | sed -n '/[Yy]es/p'
yes
- 也可以在单个表达式中用多个字符组。
$ echo "Yes" | sed -n '/[Yy][Ee][Ss]/p'
Yes
$ echo "yEs" | sed -n '/[Yy][Ee][Ss]/p'
yEs
$ echo "yeS" | sed -n '/[Yy][Ee][Ss]/p'
yeS
- 在不太确定某个字符的大小写时,字符组会非常有用。
$ echo "Yes" | sed -n '/[Yy]es/p'
Yes
$ echo "yes" | sed -n '/[Yy]es/p'
yes
- 字符组不必只含有字母,也可以在其中使用数字。
$ cat data7
This line doesn't contain a number.
This line has 1 number on it.
This line a number 2 on it.
This line has a number 4 on it.
$ sed -n '/[0123]/p' data7
This line has 1 number on it.
This line a number 2 on it.
这个正则表达式模式匹配了任意含有数字0、1、2或3的行。含有其他数字以及不含有数字的行都会被忽略掉。
-
可以将字符组组合在一起,以检查数字是否具备正确的格式。
比如我们的邮编是六位的数字,那么下面的命令就可以把不是六位的给筛选掉`
$ cat data8
60633
46201
223001
4353
22203
$ sed -n '/^[0123456789][0123456789][0123456789][0123456789][0123456789]$/p' data8
60633
46201
22203
- 字符组的一个极其常见的用法是解析拼错的单词,比如用户表单输入的数据。可以创建正则表达式来接受数据中常见的拼写错误。
$ cat data9
I need to have some maintenence done on my car.
I'll pay that in a seperate invoice.
After I pay for the maintenance my car will be as good as new.
#换行能不能代替为空格?
$ sed -n '/maint[ea]n[ae]nce/p/sep[ea]r[ea]te/p' data9
I need to have some maintenence done on my car.
I'll pay that in a seperate invoice.
After I pay for the maintenance my car will be as good as new.
两个 sed 打印命令利用正则表达式字符组来帮助找到文本中拼错的单词maintenance和separate。同样的,正则表达式模式也能匹配正确拼写的maintenance。
3.5 排除型字符组
- 在正则表达式模式中,也可以反转字符组的作用。可以寻找组中没有的字符,而不是去寻找组中含有的字符。要这么做的话,只要在字符组的开头加个脱字符。
$ cat data6
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
This test is at line four.
$ sed -n '/[^ ch]at/p' data6
首先筛选出含有at的行,之后在这些行必须还要满足条件:at的前面没有(空格)或
c
或h
,所以三者均被排除。
3.5 区间
之前演示邮编的例子的时候,必须在每个字符组中列出所有可能的数字,这实在有点麻烦。好在有一种便捷的方法可以让人免受这番劳苦。可以用单破折线符号在字符组中表示字符区间。只需要指定区间的第一个字符单破折线以及区间的最后一个字符就行了。
- 根据Linux系统采用的字符集(参见第2章),正则表达式会包括此区间内的任意字符。现在你可以通过指定数字区间来简化邮编的例子。
$ sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data8
60633
46201
45902
- 同样的方法也适用于字母。
$ sed -n '/[c-h]at/p' data6
The cat is sleeping.
That is a very nice hat.
新的模式 [c-h]at 匹配了首字母在字母c和字母h之间的单词。这种情况下,只含有单词 at的行将无法匹配该模式。
- 还可以在单个字符组指定多个不连续的区间。
$ sed -n '/[a-ch-m]at/p' data6
The cat is sleeping.
That is a very nice hat.
该字符组允许区间ac、hm中的字母出现在 at 文本前,但不允许出现d~g的字母。
$ echo "I'm getting too fat." | sed -n '/[a-ch-m]at/p'
该模式不匹配 fat 文本,因为它没在指定的区间。
3.6 特殊的字符组
- 除了定义自己的字符组外,BRE还包含了一些特殊的字符组,可用来匹配特定类型的字符。
-
表20-1介绍了可用的BRE特殊的字符组。
- 可以在正则表达式模式中将特殊字符组像普通字符组一样使用。
$ echo "abc" | sed -n '/[[:digit:]]/p'
$ echo "abc" | sed -n '/[[:alpha:]]/p'
abc
$ echo "abc123" | sed -n '/[[:digit:]]/p'
abc123
$ echo "This is, a test" | sed -n '/[[:punct:]]/p'
This is, a test
$ echo "This is a test" | sed -n '/[[:punct:]]/p'
- 使用特殊字符组可以很方便地定义区间。可以用
[[:digit:]]
来代替区间[0-9]。
3.7 星号
- 在字符后面放置星号表明该字符必须在匹配模式的文本中出现0次或多次,即可出现可不出现。
$ echo "ik" | sed -n '/ie*k/p'
ik
$ echo "iek" | sed -n '/ie*k/p'
iek
$ echo "ieek" | sed -n '/ie*k/p'
ieek
$ echo "ieeek" | sed -n '/ie*k/p'
ieeek
- 这个模式符号广泛用于处理有常见拼写错误或在不同语言中有拼写变化的单词。举个例子,如果需要写个可能用在美式或英式英语中的脚本,可以这么写:
$ echo "I'm getting a color TV" | sed -n '/colou*r/p'
I'm getting a color TV
$ echo "I'm getting a colour TV" | sed -n '/colou*r/p'
I'm getting a colour TV
模式中的 u* 表明字母u可能出现或不出现在匹配模式的文本中。
- 类似地,如果你知道一个单词经常被拼错,你可以用星号来允许这种错误。
$ echo "I ate a potatoe with my lunch." | sed -n '/potatoe*/p'
I ate a potatoe with my lunch.
echo "I ate a potato with my lunch." | sed -n '/potatoe*/p'
I ate a potato with my lunch.
在可能出现的额外字母后面放个星号将允许接受拼错的单词。
- 另一个方便的特性是将点号特殊字符和星号特殊字符组合起来。这个组合能够匹配任意数量的任意字符。它通常用在数据流中两个可能相邻或不相邻的文本字符串之间。
$ echo "this is a regular pattern expression" | sed -n '/regular.*expression/p'
this is a regular pattern expression
可以使用这个模式轻松查找可能出现在数据流中文本行内任意位置的多个单词。
- 星号还能用在字符组上。它允许指定可能在文本中出现多次的字符组或字符区间。
$ echo "bt" | sed -n '/b[ae]*t/p'
bt
$ echo "bat" | sed -n '/b[ae]*t/p'
bat
$ echo "bet" | sed -n '/b[ae]*t/p'
bet
$ echo "btt" | sed -n '/b[ae]*t/p'
btt
$ echo "baat" | sed -n '/b[ae]*t/p'
baat
$ echo "baaeeet" | sed -n '/b[ae]*t/p'
baaeeet
$ echo "baeeaeeat" | sed -n '/b[ae]*t/p'
baeeaeeat
$ echo "baakeeet" | sed -n '/b[ae]*t/p'
只要a和e字符以任何组合形式出现在 b 和 t 字符之间(就算完全不出现也行),模式就能够匹配。如果出现了字符组之外的字符,该模式匹配就会不成立。
三、扩展正则表达式
POSIX ERE模式包括了一些可供Linux应用和工具使用的额外符号。gawk程序能够识别ERE模式,但sed编辑器不能。
警告:sed编辑器和gawk程序的正则表达式引擎之间是有区别的。gawk程序可以使用大多数扩展正则表达式模式符号,并且能提供一些额外过滤功能,而这些功能都是sed编辑器所不具备的。但正因为如此,gawk程序在处理数据流时通常才比较慢。
本节将介绍可用在gawk程序脚本中的较常见的ERE模式符号。
1. 问号
- 问号类似于星号,不过有点细微的不同。问号表明前面的字符可以出现0次或1次,但只限于此。它不会匹配多次出现的字符。
$ echo "bt" | gawk '/be?t/{print $0}'
bt
$ echo "bet" | gawk '/be?t/{print $0}'
bet
$ echo "beet" | gawk '/be?t/{print $0}'
$ echo "beeet" | gawk '/be?t/{print $0}'
- 与星号一样,你可以将问号和字符组一起使用。
$ echo "bt" | gawk '/b[ae]?t/{print $0}'
bt
$ echo "bat" | gawk '/b[ae]?t/{print $0}'
bat
$ echo "bot" | gawk '/b[ae]?t/{print $0}'
$ echo "bet" | gawk '/b[ae]?t/{print $0}'
bet
$ echo "baet" | gawk '/b[ae]?t/{print $0}'
$ echo "beat" | gawk '/b[ae]?t/{print $0}'
$ echo "beet" | gawk '/b[ae]?t/{print $0}'
2. 加号
- 加号是类似于星号的另一个模式符号,但跟问号也有不同。加号表明前面的字符可以出现1
次或多次,但必须至少出现1次。如果该字符没有出现,那么模式就不会匹配。
#e至少要出现一次
$echo "bt" | gawk '/be+t/{print $0}'
$ echo "bet" | gawk '/be+t/{print $0}'
beeet
$ echo "beet" | gawk '/be+t/{print $0}'
beet
#如果字符组中定义的任一字符出现了,文本就会匹配指定的模式。
$$ echo "beat" | gawk '/b[ae]+t/{print $0}'
beat
$ echo "beet" | gawk '/b[ae]+t/{print $0}'
beet
$ echo "beeat" | gawk '/b[ae]+t/{print $0}'
beeat
3.使用花括号
-
ERE中的花括号允许你为可重复的正则表达式指定一个上限。这通常称为间隔(interval)。可以用两种格式来指定区间。
- m :正则表达式准确出现 m 次。
- m, n :正则表达式至少出现 m 次,至多 n 次。
这个特性可以精确调整字符或字符集在模式中具体出现的次数。
警告:默认情况下,gawk程序不会识别正则表达式间隔。必须指定gawk程序的 --re- interval命令行选项才能识别正则表达式间隔。
- 这里有个使用简单的单值间隔的例子。
$ echo "bt" | gawk --re-interval '/be{1}t/{print $0}'
$ echo "bet" | gawk --re-interval '/be{1}t/{print $0}'
bet
$ echo "beet" | gawk --re-interval '/be{1}t/{print $0}'
通过指定间隔为1,限定了该字符在匹配模式的字符串中出现的次数。如果该字符出现多次或不出现,模式匹配就不成立。
- 很多时候,同时指定下限和上限也很方便。
$ echo "bt" | gawk --re-interval '/be{1,2}t/{print $0}'
$ echo "bet" | gawk --re-interval '/be{1,2}t/{print $0}'
bet
$ echo "beet" | gawk --re-interval '/be{1,2}t/{print $0}'
beet
$ echo "beeet" | gawk --re-interval '/be{1,2}t/{print $0}'
字符 e 可以出现1次或2次,这样模式就能匹配;否则,模式无法匹配。
- 间隔模式匹配同样适用于字符组。
$ echo "bt" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
$ echo "bat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bat
$ echo "bet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bet
$ echo "beat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beat
$ echo "beet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beet
$ echo "beeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beeat
$ echo "baeet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
baeet
$ echo "baeaet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
baeaet
$ echo "baeaeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
如果字母a或e在文本模式中只出现了1~2次,则正则表达式模式匹配;否则,模式匹配失败。
4. 管道符号
-
管道符号允许你在检查数据流时,用逻辑 OR 方式指定正则表达式引擎要用的两个或多个模式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本匹配失败。
使用管道符号的格式如下:expr1|expr2|...
这里有个例子。
$ echo "The cat is asleep" | gawk '/cat|dog/{print $0}'
The cat is asleep
$ echo "The dog is asleep" | gawk '/cat|dog/{print $0}'
The dog is asleep
$ echo "The sheep is asleep" | gawk '/cat|dog/{print $0}'
这个例子会在数据流中查找正则表达式 cat 或 dog 。正则表达式和管道符号之间不能有空格,否则它们也会被认为是正则表达式模式的一部分。
- 管道符号两侧的正则表达式可以采用任何正则表达式模式(包括字符组)来定义文本。
$ echo "He has a hat." | gawk '/[ch]at|dog/{print $0}'
He has a hat.
5. 表达式分组
- 正则表达式模式也可以用圆括号进行分组。当你将正则表达式模式分组时,该组会被视为一个标准字符。可以像对普通字符一样给该组使用特殊字符。举个例子:
$ echo "Sat" | gawk '/Sat(urday)?/{print $0}'
Sat
$ echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
Saturday
结尾的 urday 分组以及问号,使得模式能够匹配完整的 Saturday 或缩写 Sat 。
- 将分组和管道符号一起使用来创建可能的模式匹配组是很常见的做法。
$ echo "cat" | gawk '/(c|b)a(b|t)/{print $0}'
cat
$ echo "cab" | gawk '/(c|b)a(b|t)/{print $0}'
cab
$ echo "bat" | gawk '/(c|b)a(b|t)/{print $0}'
bat
$ echo "bab" | gawk '/(c|b)a(b|t)/{print $0}'
bab
$ echo "tab" | gawk '/(c|b)a(b|t)/{print $0}'
$ echo "tac" | gawk '/(c|b)a(b|t)/{print $0}'