shell脚本编程笔记(十)—— 正则表达式

一、 定义

正则表达式是你所定义的模式模板 (pattern template),利用通配符来描述数据流中的一个或多个字符。Linux工具(比如sedgawk)能够在处理数据时用它对数据进行模式匹配。如果数据匹配模式,它就会被接受并进一步处理;否则,它就会被滤掉。

二、 正则表达式的类型

使用正则表达式最大的问题在于有不止一种类型的正则表达式。Linux中的不同应用程序可能会用不同类型的正则表达式。这其中包括编程语言(Java PerlPython)、 Linux实用工具(sedgawkgrep)以及主流应用(MySQLPostgreSQL)。

正则表达式是通过正则表达式引擎regular expression engine)实现的。正则表达式引擎是一套底层软件,负责解释正则表达式模式并使用这些模式进行文本匹配。

在Linux中,有两种流行的正则表达式引擎:

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

多数Linux工具都至少符合BRE引擎规范,能够识别该规范定义的所有模式符号。遗憾的是,有些工具(比如sed)只符合了BRE引擎规范的子集。这是出于速度方面的考虑导致的,因为sed希望能尽可能快地处理数据流中的文本。gawk程序则用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}'
$

正则表达式并不关心模式在数据流中的位置,它也不关心模式出现了多少次。一旦匹配了文本字符串中任意位置上的模式,它就会将该字符串传回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

#也可以在正则表达式中使用空格和数字。
$ echo "This is line number 1" | sed -n '/ber 1/p'
This is line number 1

甚至可以创建匹配多个连续空格的正则表达式模式。

$ 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

尽管正斜线不是正则表达式的特殊字符,但如果它出现在sedgawk程序的正则表达式中,你就会得到一个错误。

$ echo "3 / 2" | sed -n '///p'
sed: -e expression #1, char 2: No previous regular expression

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

$ echo "3 / 2" | sed -n '/\//p'
3 / 2

3. 锚字符

有两个特殊字符可以用来将模式锁定在数据流中的行首或行尾。

1)锁定在行首

脱字符(^)定义从数据流中文本行的行首开始的模式。如果模式出现在行首之外的位置,正则表达式模式则无法匹配。要用脱字符,就必须将它放在正则表达式中指定的模式前面。

$ echo "The book store" | sed -n '/^book/p'
$
$ echo "Books are great" | sed -n '/^Book/p'
Books are great

如果你将脱字符放到模式开头之外的其他位置,那么它就跟不再是特殊字符了:

$ echo "This ^ is a test" | sed -n '/s ^/p'
This ^ is a test

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)组合锚点

在一些常见情况下,可以在同一行中将行首锚点和行尾锚点组合在一起使用。

  • 第一种情况,查找只含有特定文本模式的数据行。
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
  • 第二种情况,将两个锚点直接组合在一起,之间不加任何文本。这样可以过滤出数据流中的空白行,还可以再用d命令删除空白行。
$ 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.

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放在行首就不会匹配该模式了。

5. 字符组

字符组(character class)可以定义用来匹配文本模式中某个位置的一组字符。如果字符组中的某个字符出现在了数据流中,那它就匹配了该模式。使用方括号来定义一个字符组,方括号中包含所有你希望出现在该字符组中的字符。然后你可以在模式中使用整个组,就跟使用其他通配符一样。

在不太确定某个字符的大小写时,字符组会非常有用。

$ 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 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.

6. 排除型字符组

在字符组的开头加脱字符(^),可以寻找组中没有的字符,相当于非操作。

$ sed -n '/[^ch]at/p' data6
This test is at line four.

通过排除型字符组,正则表达式模式会匹配ch之外的任何字符以及文本模式。由于空格字符属于这个范围,它通过了模式匹配。即使是排除,字符组仍然必须匹配一个字符,所以以at开头的行仍然未能匹配模式。

7. 区间

在每个字符组中列出所有可能的数字,这实在有点麻烦,可以用单破折线符号在字符组中表示字符区间。只需要指定区间的第一个字符、单破折线以及区间的最后一个字符就行了。

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.

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

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

该字符组允许区间a~c h~m中的字母出现在at文本前,但不允许出现d~g的字母。

$ echo "I'm getting too fat." | sed -n '/[a-ch-m]at/p'
$

该模式不匹配fat文本,因为它没在指定的区间。

8. 特殊的字符组

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'
$

9. 星号

在字符后面放置星号表明该字符必须在匹配模式的文本中出现0次或多次

$ echo "ik" | sed -n '/ie*k/p'
ik
$ echo "iek" | sed -n '/ie*k/p'
iek
$ echo "ieeeek" | sed -n '/ie*k/p'
ieeeek

这个模式符号广泛用于处理有常见拼写错误或在不同语言中有拼写变化的单词。举个例子,如果需要写个可能用在美式或英式英语中的脚本,可以这么写:

$ 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

另一个方便的特性是将.和*组合起来,这个组合能够匹配任意数量的任意字符。它通常用在数据流中两个可能相邻或不相邻的文本字符串之间。

$ 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 "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 "baakeeet" | sed -n '/b[ae]*t/p'
$

三、 扩展正则表达式

POSIX ERE模式包括了一些可供Linux应用和工具使用的额外符号。 gawk程序能够识别ERE模式,但sed编辑器不能。

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 "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}'
$

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. 使用花括号

{}允许你为可重复的正则表达式指定一个上限,这通常称为间隔(interval),这可以精确调整字符或字符集在模式中具体出现的次数。

可以用两种格式来指定区间:

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

gawk默认不会识别正则表达式间隔,必须指定gawk--re- interval 选项才能识别正则表达式间隔。

通过指定间隔为1,限定了该字符在匹配模式的字符串中出现的次数。如果该字符出现多次,模式匹配就不成立。

$ 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}'
$

很多时候,同时指定下限和上限也很方便。

$ 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}'
$

间隔模式匹配同样适用于字符组。

$ 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

4. 管道符号

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

$ 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}'
$

正则表达式和管道符号之间不能有空格,否则它们也会被认为是正则表达式模式的一部分。管道符号两侧可以采用任何正则表达式模式来定义文本。

$ 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

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

$ 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}'
$

四、 正则表达式实战

1. 目录文件计数

我们先看一个shell脚本,它会对PATH环境变量中定义的目录里的可执行文件进行计数。

首先你得将PATH变量解析成单独的目录名。

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

PATH的每个路径由冒号分隔,要获取可在脚本中使用的目录列表,就必须用空格来替换冒号。

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

分离出目录之后,你就可以使用标准for语句中来遍历每个目录。

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

一旦获得了单个目录,就可以用ls命令来列出每个目录中的文件,并用另一个for语句来遍历每个文件,为文件计数器增值。

#!/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

有另一个思路,只用一个循环,ll 每个目录|wc –l,然后减1。要减1是因为ll最后会输出一个total 文件总大小,会导致比实际文件数多一行,需要去掉。

#!/bin/bash
mypath=$(echo $PATH | sed 's/:/ /g')
count=0

for directory in $mypath
do
rows=$(ls -l $directory/|wc -l) #注意$directory后一定要有/
count=$[$rows - 1]
echo "$directory - $count" #ls -l会输出多一行total,需要去掉
count=0
rows=0
done

由于/root/bin目录不存在,会变为0-1=-1,当然你可以先判断目录是不是存在再计数。

2. 验证电话号码

正则表达式通常用于验证数据,确保脚本中数据格式的正确性。

一个常见的数据验证应用就是检查电话号码。数据输入表单通常会要求填入电话号码,而用户输入格式错误的电话号码是常有的事。在美国,电话号码有几种常见的形式:

(123)456-7890
(123) 456-7890
123-456-7890
123.456.7890

这样用户在表单中输入的电话号码就有4种可能,正则表达式必须足够强大,才能处理每一种情况。

在构建正则表达式时,最好从左手边开始,然后构建用来匹配可能遇到的字符的模式。

在这个例子中,电话号码中可能有也可能没有左圆括号。这可以用如下模式来匹配:

^\(?

紧接着就是3位区号。在美国,区号以数字2开始(没有以数字01开始的区号),最大可到9

# 第一个字符是2~9的数字,后跟任意两位数字
[2-9][0-9]{2}

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

\)?

在区号后,存在如下可能:有一个空格,没有空格,有一条单破折线或一个点。你可以对它们使用管道符号,并用圆括号进行分组。你必须将点字符转义,否则它会被解释成可匹配任意字符。

(| |-|\.)

紧接着是3位电话交换机号码。这里没什么需要特别注意的。

[0-9]{3}

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

( |-|\.)

最后,必须在字符串尾部匹配4位本地电话分机号。

[0-9]{4}$

完整的模式如下:

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

gawk程序中使用正则表达式间隔时,必须使用--re-interval命令行选项,否则就没法得到正确的结果。

cat phonelist
000-000-0000
123-456-7890
212-555-1234
(317)555-1234
(202) 555-9876
33523
1234567890
234.123.4567
$ cat isphone
#!/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}'

$ cat phonelist | ./isphone
212-555-1234
(317)555-1234
(202) 555-9876
234.123.4567

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值