正则表达式
一、什么是正则表达式
1. 定义
正则表达式是你所定义的模式模板(pattern template),Linux工具可以用它来过滤文本。Linux工具(比如sed编辑器或gawk程序)能够在处理数据时使用正则表达式对数据进行模式匹配。如果数据匹配模式,它就会被接受并进一步处理;如果数据不匹配模式,它就会被滤掉。
shell脚本中成功运用sed编辑器和gawk程序的关键在于熟练使用正则表达式。
正则表达式模式利用通配符来描述数据流中的一个或多个字符。Linux中有很多场景都可以使用通配符来描述不确定的数据。
比如ls命令中使用通配符列出文件和目录的例子
$ 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
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.纯文本
下面的命令演示了在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程序都没打印该行。
正则表达式的原则:
- 正则表达式模式都区分大小写。
- 你不用写出整个单词。只要定义的文本出现在数据流中,正则表达式就能够匹配。
- 可以在正则表达式中使用空格和数字。
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.
$
只要模式出现在新行的行首,脱字符就能够发现它。
如果你将脱字符放到模式开头之外的其他位置,那么它就跟普通字符一样,不再是特殊字符了:
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 another test line.
$ sed '/^
$/d' data5
This is one test line.
This is another test line.
$
定义的正则表达式模式会查找行首和行尾之间什么都没有的那些行。由于空白行在两个换行符之间没有文本,刚好匹配了正则表达式模式。sed编辑器用删除命令d来删除匹配该正则表达式模式的行,因此删除了文本中的所有空白行。这是从文档中删除空白行的有效方法。
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.
$
5. 字符组
点号特殊字符在匹配某个字符位置上的任意字符时很有用。但如果你想要限定待匹配的具体字符呢?在正则表达式中,这称为字符组。
使用方括号来定义一个字符组。方括号中包含所有你希望出现在该字符组中的字符。然后你可以在模式中使用整个组,就跟使用其他通配符一样。
下面是个创建字符组的例子。
$ sed -n '/[ch]at/p' data6
The cat is sleeping.
That is a very nice hat.
$
这里我们希望匹配的是含有单词cat 或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
$
字符组不必只含有字母,也可以在其中使用数字。
字符组的一个极其常见的用法是解析拼错的单词,比如用户表单输入的数据。你可以创建正则表达式来接受数据中常见的拼写错误。
$ 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。
6. 排除型字符组
在正则表达式模式中,也可以反转字符组的作用。可以寻找组中没有的字符,而不是去寻找字符组中含有的字符。要这么做的话,只要在字符组的开头加个脱字符。
$ sed -n '/[^
ch]at/p' data6
This test is at line four.
$
通过排除型字符组,正则表达式模式会匹配c或h之外的任何字符以及文本模式。由于空格字符属于这个范围,它通过了模式匹配。但即使是排除,字符组仍然必须匹配一个字符,所以以at开头的行仍然未能匹配模式。
7. 区间
可以用单破折线符号在字符组中表示字符区间。正则表达式会包括此区间内的任意字符。适用于数字,字母。
$ cat data8
60633
46201
223001
4353
22203
$ 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.
$
8. 星号
在字符后面放置星号表明该字符必须在匹配模式的文本中出现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 "ieeeek" | sed -n '/ie*k/p'
ieeeek
$
8.1 用法一
这个模式符号广泛用于处理有常见拼写错误或在不同语言中有拼写变化的单词。举个例子,如果需要写个可能用在美式或英式英语中的脚本,可以这么写:
$ 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可能出现或不出现在匹配模式的文本中。类似地,如果你知道一个单词经常被拼错,你可以用星号来允许这种错误。
8.2 用法二
另一个方便的特性是将点号特殊字符和星号特殊字符组合起来。这个组合能够匹配任意数量的任意字符。它通常用在数据流中两个可能相邻或不相邻的文本字符串之间。
$ echo "this is a regular pattern expression" | sed -n '
> /regular.*expression/p'
this is a regular pattern expression
$
可以使用这个模式轻松查找可能出现在数据流中文本行内任意位置的多个单词。
8.3 用法三
星号还能用在字符组上。它允许指定可能在文本中出现多次的字符组或字符区间。
$ 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'
$
9. 特殊的字符组
除了定义自己的字符组外,BRE还包含了一些特殊的字符组,可用来匹配特定类型的字符。
三、 扩展正则表达式(ERE模式)
POSIX ERE模式包括了一些可供Linux应用和工具使用的额外符号。gawk程序能够识别ERE模式,但sed编辑器不能。
下面将介绍可用在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次。如果该字符没有出现,那么模式就不会匹配。
$ echo "beeet" | gawk '/be+t/{print $0}'
beeet
$ echo "beet" | gawk '/be+t/{print $0}'
beet
$ echo "bet" | gawk '/be+t/{print $0}'
bet
$ echo "bt" | gawk '/be+t/{print $0}'
$
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次,这样模式就能匹配;否则,模式无法匹配。
间隔模式匹配同样适用于字符组。
4.管道符号
管道符号允许你在检查数据流时,用逻辑OR方式指定正则表达式引擎要用的两个或多个模式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本匹配失败。
$ 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。正则表达式和管道符号之间不能有空格,否则它们也会被认为是正则表达式模式的一部分。
5.表达式分组
正则表达式模式也可以用圆括号进行分组。当你将正则表达式模式分组时,该组会被视为一个标准字符。可以像对普通字符一样给该组使用特殊字符。举个例子:
$ echo "Sat" | gawk '/Sat(urday)?/{print $0}'
Sat
$ echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
Saturday
$
结尾的urday分组以及问号,使得模式能够匹配完整的Saturday或缩写Sat。
将分组和管道符号一起使用来创建可能的模式匹配组是很常见的做法。