一、什么是正则表达式

模式模板(pattern template),用它来过滤文本。如果数据匹配模式,它就会被接受并进一步处理;如果数据不匹配模式,它就会被滤掉。

正则表达式是通过正则表达式引擎(regular expression engine)实现的。在Linux中,有两种流行的正则表达式引擎:

  • POSIX基础正则表达式(basic regular expression,BRE)引擎
  • POSIX扩展正则表达式(extended regular expression,ERE)引擎

POSIX BRE引擎通常出现在依赖正则表达式进行文本过滤的编程语言中。它为常见模式提供了高级模式符号和特殊符号,比如匹配数字、单词以及按字母排序的字符。

BRE 模式

1.纯文本

正则表达式模式都区分大小写。这意味着它们只会匹配大小写也相符的模式。完整的正则表达式文本并未在数据流中出现,因此匹配失败,sed编辑器不会显示任何文本。

1
2
$ echo "The books are expensive" | sed -n '/book/p' 
The books are expensive

2. 特殊字符

1
.*[]^${}\+?|()

要匹配特殊字符,必须转义反斜线(\)。

要使用正斜线,也需要进行转义。

1
2
$ echo "\ is a special character" | sed -n '/\\/p'
$ echo "3 / 2" | sed -n '/\//p'

3.锚字符

只要模式出现在数据流中的任何地方,它就能匹配。

  1. 锁定在行首:脱字符(^),在每个由换行符决定的新数据行的行首检查模式。

    1
    2
    $ echo "Books are great" | sed -n '/^Book/p' 
    Books are great

    将脱字符放到模式开头之外的其他位置,那么它就跟普通字符一样,不再是特殊字符了

  2. 锁定在行尾:美元符($

    1
    $ echo "This is a good book" | sed -n '/book$/p'
  3. 组合锚点

    1
    2
    3
    4
    5
    6
    7
    8
    $ 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

    将两个锚点直接组合在一起,样过滤出数据流中的空白行

    1
    2
    #ed编辑器用删除命令d来删除匹配该正则表达式
    $ sed '/^$/d' data5

4.点号字符

匹配除换行符之外的任意单个字符。它必须匹配一个字符,在点号字符的位置没有字符,那么模式就不成立。

1
2
3
4
5
6
7
8
9
10
$ 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前面的空格刚好匹配了该模式。

5.字符组

要限定待匹配的具体字符呢?

使用方括号来定义一个字符组。方括号中包含所有你希望出现在该字符组中的字符。

1
2
3
$ sed -n '/[ch]at/p' data6 
The cat is sleeping.
That is a very nice hat.

可以在单个表达式中用多个字符组

1
$ echo "Yes" | sed -n '/[Yy][Ee][Ss]/p'

6.排除型字符组

寻找组中没有的字符

1
$ sed -n '/[^ch]at/p' data6

正则表达式模式会匹配c或h之外的任何字符以及文本模式。由于空格字符属于这个范围,它通过了模式匹配。但即使是排除,字符组仍然必须匹配一个字符

7.区间

用单破折线符号在字符组中表示字符区间。

1
2
3
4
5
6
7
8
9
10
11
$ cat data8 
60633
46201
223001
4353
22203
#指定数字区间来简化邮编的例子。
$ sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data8
60633
46201
45902

还可以在单个字符组指定多个不连续的区间。

1
2
#允许区间a~c、h~m中的字母出现在at文本前
$ sed -n '/[a-ch-m]at/p' data6

8.特殊的字符组

用来匹配特定类型的字符。

描 述
[[:alpha:]] 匹配任意字母字符,不管是大写还是小写
[[:alnum:]] 匹配任意字母数字字符09、AZ或a~z
[[:blank:]] 匹配空格或制表符
[[:digit:]] 匹配0~9之间的数字
[[:lower:]] 匹配小写字母字符a~z
[[:print:]] 匹配任意可打印字符
[[:punct:]] 匹配标点符号
[[:space:]] 匹配任意空白字符:空格、制表符、NL、FF、VT和CR
[[:upper:]] 匹配任意大写字母字符A~Z

9.星号

在字符后面放置星号表明该字符必须出现0次或多次

1
2
3
$ echo "ik" | sed -n '/ie*k/p'
$ echo "iek" | sed -n '/ie*k/p'
$ echo "ieek" | sed -n '/ie*k/p'

点号特殊字符和星号特殊字符组合起来。匹配任意数量任意字符。通常用在数据流中两个可能相邻或不相邻的文本字符串之间。

1
2
$ echo "this is a regular pattern expression" | sed -n ' 
> /regular.*expression/p'

可以使用这个模式轻松查找可能出现在数据流中文本行内任意位置的多个单词。

星号还能用在字符组上。它允许指定可能在文本中出现多次的字符组或字符区间。

1
$ echo "bt" | sed -n '/b[ae]*t/p'

扩展正则表达式

gawk程序能够识别ERE模式,但sed编辑器不能。

1.问号

问号表明前面的字符可以出现0次1次

将问号和字符组一起使用。如果字符组中的字符出现了0次或1次,模式匹配就成立。但如果两个字符都出现了,或者其中一个字符出现了2次,模式匹配就不成立。

1
2
3
4
5
6
$ 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}'
$

2.加号

加号表明前面的字符可以出现1次多次,但必须至少出现1次。如果该字符没有出现,那么模式就不会匹配。

1
2
3
4
$ echo "bet" | gawk '/be+t/{print $0}' 
bet
$ echo "bt" | gawk '/be+t/{print $0}'
$

同样适用于字符组,与星号和问号的使用方式相同。

3.使用花括号

允许为可重复的正则表达式指定一个上限。这通常称为间隔(interval)。可以用两种格式来指定区间

  • m:正则表达式准确出现m次。
  • m, n:正则表达式至少出现m次,至多n次。

gawk程序不会识别正则表达式间隔。必须指定gawk程序的–re- interval命令行选项才能识别正则表达式间隔。

1
2
3
4
$ echo "bt" | gawk --re-interval '/be{1,2}t/{print $0}' 
$
$ echo "bet" | gawk --re-interval '/be{1,2}t/{print $0}'
bet

4.管道符号

用逻辑OR方式指定正则表达式引擎要用的两个或多个模式。

1
expr1|expr2|...
1
$ echo "The cat is asleep" | gawk '/cat|dog/{print $0}

5.表达式分组

用圆括号进行分组。当你将正则表达式模式分组时,该组会被视为一个标准字符。

1
2
3
4
$ echo "Sat" | gawk '/Sat(urday)?/{print $0}' 
Sat
$ echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
Saturday

将分组和管道符号一起使用来创建可能的模式匹配组是很常见的做法。

1
2
3
4
$ echo "cat" | gawk '/(c|b)a(b|t)/{print $0}' 
cat
$ echo "cab" | gawk '/(c|b)a(b|t)/{print $0}'
cab

二、正则表达式例子

1.目录文件计数

思路

对PATH环境变量中定义的目录里的可执行文件进行计数。

1
2
$ echo $PATH 
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/ local/games

要获取可在脚本中使用的目录列表,就必须用空格来替换冒号。

1
2
$ echo $PATH | sed 's/:/ /g' 
/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin /usr/games /usr/local/games

以使用标准for语句中(参见第13章)来遍历每个目录。

1
2
3
4
5
mypath=$(echo $PATH | sed 's/:/ /g') 
for directory in $mypath
do
...
done

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash 
# count number of files in your PATH
mypath=$(echo $PATH | sed 's/:/ /g')
count=0
for directory in $mypath
do
check=$(ls $directory)
for item in $check
do
count=$[ $count + 1 ]
done
echo "$directory - $count"
count=0
done

2.验证美国电话号码

美国电话号码有这几种:

1
2
3
4
(123)456-7890 
(123) 456-7890
123-456-7890
123.456.7890

电话号码中可能有也可能没有左圆括号。

1
^\(?

紧接着就是3位区号。在美国,区号以数字2开始(没有以数字0或1开始的区号),最大可到9。要匹配区号,可以用如下模式。这要求第一个字符是2~9的数字,后跟任意两位数字。

1
[2-9][0-9]{2}

收尾的右圆括号可能存在,也可能不存在。

1
\)?

在区号后,存在如下可能:有一个空格,没有空格,有一条单破折线或一个点。4种情况

1
(| |-|\.)

是3位电话交换机号码。

1
[0-9]{3}

在电话交换机号码之后,你必须匹配一个空格、一条单破折线或一个点

1
( |-|\.)

在字符串尾部匹配4位本地电话分机号。

1
[0-9]{4}$

完整模式如下:

1
^\(?[2-9][0-9]{2}\)?(| |-|\.)[0-9]{3}( |-|\.)[0-9]{4}$

只需要在gawk程序中创建一个使用该正则表达式的简单脚本,然后用这个脚本来过滤你的电话薄。

1
2
3
4
#!/bin/bash 
# script to filter out bad phone numbers
gawk --re-interval '/^\(?[2-9][0-9]{2}\)?(| |-|\¬
[0-9]{3}( |-|\.)[0-9]{4}/{print $0}'
1
2
$ echo "317-555-1234" | ./isphone 
317-555-1234

3.解析邮件地址

邮件地址的基本格式为:

1
username@hostname

username值可用字母数字字符以及以下特殊字符:

1
2
3
4
点号
单破折线
加号
划线

hostname部分由一个或多个域名和一个服务器名组成。只允许字母数字字符以及以下特殊字符:

1
2
点号
下划线

用户名中可以有多个有效字符

1
^([a-zA-Z0-9_\-\.\+]+)@

hostname模式使用同样的方法来匹配服务器名和子域名。

1
([a-zA-Z0-9_\-\.]+)

对于顶级域名,有一些特殊的规则。顶级域名只能是字母字符,必须不少于二个字符(国家或地区代码中使用),并且长度上不得超过五个字符。

1
\.([a-zA-Z]{2,5})$

完整模式

1
^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$