IBM的LPI复习资料之LPI101-Topic103 :GNU和Unix命令(7)使用正则表达式搜索文本文件

来源:互联网 发布:hiphop服装品牌知乎 编辑:程序博客网 时间:2024/05/29 17:12

摘要: 首先学习正则表达式,然后使用它来在文件中查找。

1 概述

本文为使用正则表达式进行搜索打基础。(1) 创建简单的正则表达式(2) 使用正则表达式进行搜索(3) 在sed命令中使用正则表达式

2 准备例子文件

使用如下命令来创建本文需要的目录和文件。

mkdir -p 103-7 && cd lpi103-7 && {echo -e "1 apple\n2 pear\n3 banana" > text1echo -e "9\tplum\n3\tbanana\n10\tapple" > text2echo "This is a sentence. " !#:* !#:1->text3split -l 2 text1split -b 17 text2 y; cp text1 text1.bkpmkdir -p backupcp text1 backup/text1.bkp.2}

3 正则表达式

正则表达式根源于计算机语言理论。计算机科学专业的学生们都知道能够被正则表达式表示的语言都可以同样被有限自动机来表达。本文讲述的正则表达式用来表达更复杂的东西,这与计算机科学课堂上所学的是不一样的,尽管两者之间是紧密联系的。正则表达式(也叫做regex或regexp)是用来描述一个字符串或模式的方式,这样程序就能够从任意的文本中查找满足模式的字符串,从而成为功能强大的搜索工具。grep(generalized regular express processor)是Linux或Unix管理员工具箱的标准组成部分, 它就允许使用正则表达式来搜索文本。sed则是另一个广泛使用正则表达式的工具,它可以用来在文件或文本流中查找和替换文本。本文将帮助你更好的理解grep和sed中使用的正则表达式。另外一个深度使用正则式的工具是awk。与本系列的其他文章一样,关于正则表达式和计算机语言理论的书籍有很多。在学习正则表达式的时候,你可能会看到正则表达式的语法和前面文章提到的通配符语法有些类似,但是这种类似只是表面的,本质上两者是不同的概念。

3.1 基本构件块

大多数Linux系统上的grep支持两种正则表达式语法格式:基本语法和扩展语法。对于GNUgrep,这两者在功能上没有差异。 基本语法、以及它与扩展语法的差异将在本文讲述。正则表达式由字符和操作符组成,其中操作符由元字符组成。大多数的字符匹配它们自身,大多数的元字符使用前必须被转义。基本的操作有:

  • (1) 连接, 连接让两个正则表达式变成一个更长的正则表达式。例如字符a会匹配字符串abcdcba两次(第一个a和最后一个a)。字符b也一样会匹配两次,
  • 但是ab则只能匹配一次,ba也是匹配一次。
  • (2) 重复,*会匹配它前面的正则表达式0或多次。例如a*b将会匹配任意多个a相连然后以b结尾。要是为了匹配字面意义上的*,则需要在正则表达式中对*
  • 进行转义。注意,这里的*与通配符中的*不同,通配符中的*会匹配任意字符串。
  • (3)替换,它在使用时转义后才是元字符,例如a*\|b*c,
  • 这个正则表达式匹配的字符串包含任意多个a或者任意多个b然后紧跟着字符c。

为了防止shell对你的正则表达式做扩展,通常需要使用引号保住正则表达式。

4 搜索文件和文件系统

我们将使用前面创建的文件来做实验。请注意grep命令把一个正则表达式作为必须的参数,搜索的文件名则是可选参数,如果没有提供文件名参数,grep会从stdin中搜索,这种特性让grep可以担任过滤器的角色,如果没有匹配的行,那么grep就不会产生输出,尽管它的退出码是可以用来得到的。

ot@centos192 lpi103-7]# grep p text11 apple2 pear[root@centos192 lpi103-7]# grep pea text12 pear[root@centos192 lpi103-7]# grep "p*" text11 apple2 pear3 banana[root@centos192 lpi103-7]# grep "pp*" text11 apple2 pear[root@centos192 lpi103-7]# grep "x" text1; echo $?1[root@centos192 lpi103-7]# grep "x*" text1; echo $?1 apple2 pear3 banana0[root@centos192 lpi103-7]# cat text1 | grep "l\|n"1 apple3 banana[root@centos192 lpi103-7]# echo -e "find an \ns* here" | grep "s\*"s* here

从上面的例子中可以看出,有时候将会得到令人惊讶的结果,特别是在使用重复操作符的时候。你可能期望p*会匹配一组p,但是每一行都会匹配,因为*可以是0个。上面例子中使用了两次grep的退出码。如果至少存在一个匹配行,则返回0,如果没有匹配行,则返回1,大于1的返回值(对于GNU grep总是2)表示错误,比如提供的文件不存在。

4.1 重要的元字符

  • (1) +
  • +元字符类似于*,只是它表示1个或者1个以上。在基本语法中,它必须被转义才能作为元字符使用。
  • (2) ?
  • ?表示它前面的正则表达式是可选的,也就是出现0次或1次。这与通配符中的?意义不同。
  • (3) .
  • . 代表任意字符。最常用的模式之一就是 .*,它匹配任意长度的含有任意字符的字符串。可以看出正则中的 .? 与通配符中的 ?功能相同,而正则重的 .*与通
  • 配符中的*类似。
ot@centos192 lpi103-7]# grep "pp\+" text1 # 至少两个p1 apple[root@centos192 lpi103-7]# grep "pl\?e" text11 apple2 pear[root@centos192 lpi103-7]# grep "p.*r" text1 # p, some string thren r2 pear[root@centos192 lpi103-7]# grep "a.." text1 # a followed by two other letters1 apple3 banana

4.2 匹配一行的开头或者结尾

^匹配行的开始,$匹配行的结束。这样说来,^..b 匹开始任意两个字符跟着是b的行。ar$则匹配以ar最为最后两个字符的行。^$则匹配空行。4.3 复杂的表达式目前为止,我们看到了一个单一字符的重复。如果想让多个字符组成的字符串重复呢?这就需要使用(),在基本语法中,需要转义才能把()当作元字符使用。你可能不想使用|操作符来查找某一组字符,这正是字符组的用武之地。元字符是[],这个元字符可以直接使用,不需要转义。[]里面的表达式与正则式完全不同,其元字符含义也不同,请一定注意。看例子吧。

[root@centos192 lpi103-7]# grep "\(an\)\+" text1 # find at least 1 an3 banana[root@centos192 lpi103-7]# grep "an\(an\)\+" text1 # find at least 2 an's3 banana[root@centos192 lpi103-7]# grep "[3p]" text1 # find 3 or p1 apple2 pear3 banana[root@centos192 lpi103-7]# echo -e "find an\ns* here\nsomwhere." | grep"s[.*]"s* here[root@centos192 lpi103-7]# echo -e "find an\n * in position 2." | grep ".[.*]" * in position 2.

对于字符组来说,还有很多有趣的地方。(1)范围表达式两个字符中间通过-连接就是范围表达式。如0-9表示数字,0-aa-fA-F表示16进制数字。注意范围表达式是与地域有关的。(2)命名类对一些经常使用的字符类进行了命名,这就是命名类。命名类以[:开头,以:]结束。它们可以用在字符组里面。例如[:alnum:] 字母数字字符[:blank:] 空格和tab[:digit:] 数字,与0-9含义相同[:upper:]和[:lower:] 大写字符和小写字符(3)取反如果^出现在[]里面的第一个位置,那么它表示取反的意思,也就是字符不匹配字符组里的字符。如果想匹配字面上的^,则一定不要把^放到[]内的第一个位置。符号]会结束一个字符组,除非把他放到第一个位置。

字符组、正则表达式、通配符,这三者有些类似,但是本质上是不同的领域。下面是一些字符组的例子::nochange[root@centos192 lpi103-7]# echo -e "123\n456\n789\n0" | grep "[3-7]"123456789[root@centos192 lpi103-7]# grep "[[:digit:]][^nr]*$" text11 apple[root@centos192 lpi103-7]# grep "[[:digit:]nz][^nr]*$" text11 apple3 banananochange:

最后一个例子让你感到惊奇了吗?第一个[]表达式表示匹配任意数字、或者n、或者z,然后接着是n和r之外的其他字符任意多个。

4.4 什么被匹配了

如果能够分辨出高亮部分,比如颜色,加粗或者下划线,你可以设置GREP_COLORS环境变量来让grep把匹配部分高亮显示。默认的设置是匹配部分被加粗、颜色以红色显示。如下图。可以看出,第一行匹配的是全部,而第二行匹配的是最后的na两个字符。

如果你对于正则表达式还不熟悉,或者不确定为什么一个特定的行被grep匹配并返回,那么这种高亮显示可以给你带来很大帮助。

5 扩展的正则表达式

扩展的正则表达式语法是GNU的一个扩展。这种语法减少了对于某些元字符的转义必要,如?+ | 和{,这些元字符在基本语法中必须进行转义才能发挥元字符的作用,在扩展语法中不在需要转义,当然此消彼长,扩展语法中当需要止血元字符的字面意义时就要进行转义了。使用grep的-E(或者--extended-regerexp)选项来使用扩展语法。或者直接使用egrep命令。下面例子使用了egrep和扩展语法来实现前面grep和基本语法完成的功能。

root@centos192 lpi103-7]# # Find b followed by one or more an's and then an a[root@centos192 lpi103-7]# grep "b\(an\)\+a" text13 banana[root@centos192 lpi103-7]# egrep "b(an)+a" text13 banana

6 在文件中查找

既然我们已经学会了基本的命令,就让我们利用grep和find来在文件系统中查找东西吧。首先,grep可以一次查找多个文件。如果你提供了-n选项,那么输出中会有匹配行的行号。如果你仅仅想知道匹配了多少行,那么使用-c选项。如果只想知道那些文件匹配了,使用-l选项。下面是例子::nochange[root@centos192 lpi103-7]# grep plum *text2:9 plumyaa:9 plum[root@centos192 lpi103-7]# grep -n banana text[1-4]text1:3:3 bananatext2:2:3 banana[root@centos192 lpi103-7]# grep -c banana text[1-4]text1:1text2:1text3:0[root@centos192 lpi103-7]# grep -l pear *text1text1.bkpxaanochange:

注意上面的text3:0,这说明text3文件中匹配了0行,也就是没有匹配。grep还有一个-v选项,它的作用是输出没有匹配的行。下面的例子中,首先使用find来查找当前目录及其子目录下的所有常规文件,然后使用xargs把文件列表作为参数传递给grep来查找每个文件中banana出现的行数。最后,这个结果传给另一个grep过滤器,它使用-v选项来查找所有不是以:0结尾的行。

[root@centos192 lpi103-7]# find . -type f -print0 | xargs -0 grep -c banana |grep -v ":0$"./text2:1./text1.bkp:1./text1:1./yaa:1./backup/text1.bkp.2:1./xab:1

7 正则表达式和sed

前面我们学习sed的时候,提到过sed支持正则表达式。实际上sed命令有地址表达式和替换表达式两部分,都可以使用正则表达式。如果你仅仅是查找什么,那么你可能只是使用grep就足够了。如果你需要提取匹配的字符串然后进一步操作它,你就需要使用sed了。我们来看看sed是如何工作的。先回忆一下,在我们的例子文件text1和text2中,包含了一个数字,然后是一个空格然后是一种水果的名字。在text3中,包含了一个重复的句子。如下图所示:

ot@centos192 lpi103-7]# cat text[1-3]1 apple2 pear3 banana9plum3banana10appleThis is a sentence.  -e This is a sentence.  -e This is a sentence.  -e

首先,我们使用grep和sed来提取特定的行,这些行满足以一个或多个数字开头,然后是空白。默认sed会打印出所有行,所哟我们首先使用-n来抑止sed的输出,然后使用p命令来打印匹配的行。为了确保grep和sed使用了相同的正则表达式,我们把这个正则表达式作为变量保存。

ot@centos192 lpi103-7]#  oursearch='^[[:digit:]][[:digit:]]*[[:blank:]]'[root@centos192 lpi103-7]#  grep "$oursearch" text[1-3]text1:1 appletext1:2 peartext1:3 bananatext2:9plumtext2:3bananatext2:10apple[root@centos192 lpi103-7]# cat text[1-3] | sed -ne "/$oursearch/p"1 apple2 pear3 banana9plum3banana10apple

注意,当在多个文件中搜索时,grep会在结果中显示文件名。因为我们使用cat来为sed提供输入,所以sed没有办法知道原始的文件名。尽管结果格式不一样,但是匹配的行是完全一样的。现在假设我们只想要匹配行的第二个单词。在本例中,这个词就是水果的名字。在我们的例子中,把匹配的部分删除正好就是水果的名字了,如下:

[root@centos192 lpi103-7]# cat text[1-3] | sed -ne"/$oursearch/s/$oursearch//p"applepearbananaplumbananaapple

下面的例子展示了完成上述任务的另一种方法。首先引入了()元字符来吧整行分割成3部分:数字和空白、第二个单词,剩余部分。我们使用s命令来用第二个单词来替换整个句子,然后打印结果。你可能要使用把这个正则表达式进行变形,忽略掉第三部分\(.*\),看看那样做的结果你是否能够解释。

ot@centos192 lpi103-7]# echo "7 lemon pie" | cat - text[1-3] | sed -ne"/$oursearch/s/\($oursearch\)\([^[:blank:]]*\)\(.*\)/\2/p" | sort | uniqapplebananalemonpearplum

一些老版本的sed不支持扩展语法的正则表达式。如果你的sed支持扩展语法,那么就使用-r选项来告诉sed你正在使用扩展语法。下面脚本展示了使用扩展语法完成于上面例子一样的功能。

ot@centos192 lpi103-7]# echo "7 lemon pie" | cat - text[1-3] | sed -nre"/$oursearch/s/($oursearch)([^[:blank:]]*)(.*)/\2/p" | sort | uniqapplebananalemonpearplum

本文中的例子仅仅是使用grep和sed结合正则表达式能完成的功能的冰山一角。请参考man手册页来学习这些无价之宝的工具程序。

原创粉丝点击