在shell脚本中成功使用sed编辑器和gawk程序的关键在于熟练使用正则表达式。这能够是你,从大量数据中过滤出特定数据。
(1)什么是正则表达式
①定义
正则表达式是我们自己定义的、Linux工具用它来过滤文本的模式模板。Linux工具(比如,sed编辑器或gawk程序)能够在数据流向工具时对数据进行正则表达式模式匹配。如图所示:
例如,下面是正则表达式的一个应用,其中*是通配符:$ls -al test* -rw-rw-r-- 1 xiaoyu1 xiaoyu1 8 May 18 14:05 test -rw-rw-r-- 1 xiaoyu1 xiaoyu1 31 May 17 09:37 testfile
②正则表达式的类型
- 使用正则表达式最大的问题在于有不止一种类型的正则表达式。Linux中的不同程序可能会用不同类型的正则表达式。这包括各种应用程序,比如编程语言(Java、Perl和Python)、Linux工具(sed编辑器、gawk程序、grep工具等),以及主流应用(如MySQL和PostgreSQL数据库服务器)。
- 正则表达式是用正则表达式引擎(regular expression engine)实现的,它是解释正则表达式模式并使用这些模式进行文本匹配的底层软件。
- 在Linux中,有两种流行的正则表达式引擎:
- POSIX基本正则表达式(BRE)引擎;
- POSIX扩展正则表达式(ERE)引擎。
- 大多数Linux工具都至少符合POSIX BRE 引擎规范,能够识别它定义的所有模式符号。但,有些工具(如sed编辑器)只实现了BRE引擎规范的子集。这是速度的要求导致的,sed编辑器希望快速地处理数据流中的文本。
- POSIX BRE 引擎通常为依赖正则表达式做文本过滤的编程语言所支持。它为常见模式提供了高级模式符号和特殊符号,比如匹配数字、单词以及按字母排序的字符。gawk 程序用ERE引擎来处理它的正则表达式模式。
- 由于正则表达式的实现方法很多,很难用一个简洁的描述来涵盖所有可能的正则表达式。所以,这里我们只讨论最常见的正则表达式并演示如何在sed编辑器和gawk程序中使用它们。
(2)定义 BRE 模型
①纯文本
如何在sed编辑器中使用标准文本字符串来过滤数据,如例子所示:
$echo "This is a test" | sed -n '/test/p'
This is a test
$echo "This is a Test" | sed -n '/test/p'
$
$echo "This is a testing" | sed -n '/test/p'
This is a testing
上例子可以得出如下结论:
- 正则表达式不管模式在数据流中的位置和出现次数,一旦表达式匹配了文本字符串中的模式,它就会将该字符串传回Linux工具;
- 正则表达式模式都是区分大小写的;
- 正则表达式模式时部分匹配的;
- 正则表达式中可以使用空格和数字作为模式匹配文本,空格在整个表达式中会跟其他字符一样对待,如下例所示:
$echo "This is a testing" | sed -n '/ /p'
This is a testing
$echo "Thisisatesting" | sed -n '/ /p'
$echo "This is a testing" | sed -n '/ /p'
$
其中,第一个和第二个命令的匹配模式是一个空格;第三个命令的匹配模式是两个空格。这是用来查看文本文件中空格问题的好办法。
②特殊字符
由于我们会在正则表达式中使用文本字符,就必须注意定义文本字符时的一些特例。正则表达式识别的特殊字符包括:
.*[]^${}+?()
后续会介绍这些特殊字符在正则表达式中的作用,但现在只需记住不能在文本模式中单独使用这些字符即可。
如需使用某个特殊字符作为文本字符,需要转义。在特殊字符前添加反斜线(\),则该字符会被当做普通文本字符对待。
$echo "This is a $ testing" | sed -n '/\$/p'
This is a $ testing
$echo "This is a $ \testing" | sed -n '/\\/p'
This is a $ \testing
上例中,查找美元符号需要转义;同样查找\也需要转义。
注意,尽管斜线不是正则表达式的特殊字符,但在sed编辑器或gawk程序的正则表达式中使用会出现错误。所以,使用斜线也需要转义:
$echo "3 / 2" | sed -n '///p'
sed: -e expression #1, char 3: unknown command: `/'
$echo "3 / 2" | sed -n '/\//p'
3 / 2
③锚字符
默认情况下,正则表达式模式匹配在数据流中任意位置的数据。有两个特殊字符可以用来将模式锁定在数据流中的行首或行尾。
锁定在行首
脱字符(caret character,^)定义从文本行的行首开始匹配。如果模式位于非行首位置,则匹配不成功。脱字符,必须放在正则表达式中指定的模式前面:
$echo "This is a testing" | sed -n '/^is/p' $echo "This is a testing" | sed -n '/^This/p' This is a testing
如果脱字符放在模式中的非开头位置,它会跟普通字符一样对待:
$echo "This is a^ testing" | sed -n '/a/p' This is a^ testing $echo "This is a^ testing" | sed -n '/a^/p' This is a^ testing
锁定在行尾
跟行首查找相对的就是行尾查找。美元符号($)定义行尾锚点。
$echo "This is a good book" | sed -n '/book$/p' This is a good book $echo "This book is good" | sed -n '/book$/p' $ $echo "This is a good books" | sed -n '/book$/p' $
注意,上例中将book改为复数形式,则不能在匹配正则表达式了。因为,文本模式必须是模式要匹配的行的最后部分。
组合锚点
在一些场景下,我们需要在同一行中将行首锚点和行尾锚点组合使用。场景一:查找含有特定文本模式的数据行,
$cat data4 this is a test using... I said this is a test this is a test yes,this is a test $sed -n '/^this is a test$/p' data4 this is a test
场景二,将两个锚点直接组合在一起,中间不添加如何文本,可以过滤数据流中的空白行。如下例:
$cat data5 this is a test using... yes,this is a test $sed '/^$/d' data5 this is a test using... yes,this is a test
④点字符
点字符用来匹配任意的单字符,除了换行符。但点字符必须匹配一个字符,如果点字符的位置没有字符,那该模式不能被匹配。例子如下:
```
$cat data6
This is a test.
The cat is ...
That is a very nice hat.
This test is at line four.
at five line.
$sed -n '/.at/p' data6
The cat is ...
That is a very nice hat.
This test is at line four.
```
⑤字符组
点字符允许你匹配任意单个字符。如何只匹配给定的某些字符呢?在正则表达式中,这称为字符组(character class)。我们使用方括号([])定义字符组,在方括号中指定你要匹配的某些字符。如下例子所示:
$sed -n '/[ch]at/p' data6
The cat is ...
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
字符组中也可以使用数字:
$cat data7
line 1.
hello
line 2.
$sed -n '/[123]/p' data7
line 1.
line 2.
应用场景一,检查数字是否是正确格式化了,比如电话号码和邮编。但使用时容易出错,如下例所示:
$cat data8
60633
46201
223001
4353
22203
$sed -n '
> /[0123456789][0123456789][0123456789][0123456789][0123456789]/p
> ' data8
60633
46201
223001
22203
上例中,尽管我们过滤掉了过短而不可能是邮编的数字。但是超过6位的还存在,因为我们只定义了5个字符组。
要确保只匹配5位数,必须将匹配字符和其他字符分开,可以使用空格或指明它们就在行首和行尾的方法:
$sed -n '
> /^[0123456789][0123456789][0123456789][0123456789][0123456789]$/p
> ' data8
60633
46201
22203
字符组应用场景二,解析拼写错误的单词,比如用户表单输入的数据。
$sed -n '
> /maint[ea]n[ae]nce/p
> /sep[ea]r[ea]te/p
> ' data9
I need to maintenence done on my car.
I'll paly in a seperate.
After I pay for the maintenance my ...
本例中的两个sed打印命令利用正则表达式字符组来帮助找到文本中拼写错误的单词maintenance和separate。同样的正则表示模式也能匹配正确拼写的maintenance。
⑥排除字符组
在正则表达式中,使用脱字符(^)反转字符组的作用,即匹配不含组中字符的模式。只需要在字符组开头加个脱字符:
$cat data6
This is a test.
The cat is ...
That is a very nice hat.
This test is at line four.
at five line.
$sed -n '/[^ch]at/p' data6
This test is at line four.
注意,空格也不在字符组的范围内;但即使是排除,也要求字符组必须匹配一个字符,因此at开头的行未能匹配模式。
⑦使用区间
如我们前面演示的邮编的例子,我们必须在每个字符组中列出所有可能的数字。但我们可以使用区间来代替这一方式。
区间的写法是:[第一个字符-最后一个字符]。根据Linux系统所采用的字符集,正则表达式包含这个指定区间内的任意字符。
邮编例子可改写为:
$sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data8
60633
46201
22203
指定区间,也适用于字母:
$sed -n '/[c-z]at/p' data6
The cat is ...
That is a very nice hat.
还可以在单个字符组中指定多个不连续区间:
$sed -n '/[a-ch-m]at/p' data6
The cat is ...
That is a very nice hat.
注意,要按字符的ASCII码中的顺序指定,比如c-a这样的指定方式是无效的。
⑧特殊字符组
除了自己定义的字符组外,BRE还包含可用来匹配特殊字符类型的特殊字符组。如下表所示:
可以在正则表达式模式中将特殊字符组像普通字符组一样使用:
$echo "aBc" | sed -n '/a[[:alpha:]]c/p'
aBc
$echo "abc" | sed -n '/a[[:alpha:]]c/p'
abc
⑨星号
在字符后面放置*,说明该字符将会在匹配模式中出现0次或多次:
$echo "ab" | sed -n '/ae*b/p'
ab
$echo "aeb" | sed -n '/ae*b/p'
aeb
$echo "aeeeeb" | sed -n '/ae*b/p'
aeeeeb
这个模式符号应用场景,检查拼写错误或在不同语言中有拼写变种的单词。例如:
$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 good student." | sed -n '/a.*student/p'
This is a good student.
$echo "This is a bad student." | sed -n '/a.*student/p'
This is a bad student.
*还可以用在字符组上,它允许指定可能在文本中出现0次或多次的一组字符或一个字符区间:
$echo "bt" | sed -n '/b[ae]*t/p'
bt
$echo "bat" | sed -n '/b[ae]*t/p'
bat
$echo "baeaeeat" | sed -n '/b[ae]*t/p'
baeaeeat
(3)扩展正则表达式
POSIX ERE模式包括一些Linux应用和工具使用的若干额外符号。gawk程序能够识别ERE模式,但sed编辑器不能。
待补充。。。
(4)实用中的正则表达式
接下来将演示shell脚本中常见的一些正则表达式的例子。
- 目录文件数统计
首先,列出PATH变量中的所有目录,要意识到PATH中的每个路径都是由冒号分隔的。要获取可以在脚本中使用的目录列表,需要用空格来替换冒号。然后,使用for语句遍历每个目录中的所有文件,为每个文件将计数器+1.
$cat countfiles
#!/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
$./countfiles
/opt/node/bin - 3
/usr/local/bin - 1
/bin - 1158
/usr/bin - 1158
/usr/local/sbin - 0
/usr/sbin - 550
/usr/local/java/jdk1.7.0_75/bin - 47
/usr/local/java/jdk1.7.0_75/jre/bin - 14
/usr/local/hadoop/bin - 11
- 验证电话号码
由gawk程序完成。待补充。。。 - 解析邮件地址
待补充。。。
(5)小结