9.6 在文本文件中搜索特定模式的基础知识

上一节介绍了如何在一组文件中搜索匹配的特定模式,你应该对grep的原理有所了解了。那是对grep最基础的使用,但现在需要让搜索再复杂些,为此需要先深入理解一下grep搜索使用的匹配模式。构建这些模式使用了Linux工具箱中最强大的工具:正则表达式(regular expression或regex)。如果要充分利用grep,就需要透彻地理解正则表达式。讲述正则表达式的内容就得用一整本书,这里介绍的只是正则表达式的基本知识。

提示 想更多地学习正则表达式,可以在因特网上找到大量的学习资源,而且Sams Teach Yourself Regular Expressions in 10 Minutes(由Ben Forta撰写,ISBN:0672325667)是一本相当好的书,能够真正帮助你探索和学习正则表达式。

在刚开始使用grep时,新用户会对这个命令有好几种版本感到很困惑,如表9-1所示。 表9-1 grep的不同版本

支持的模式 grep命令选项 单独的命令
基本的正则表达式 grep -G(或 --basic-regexp grep
扩展的正则表达式 grep -E(或 --extended-regexp egrep
固定字符串的列表,匹配其中任何一项 grep -F(或 --fixed-strings fgrep
Perl正则表达式 grep -P(或 --perl-regexp 不可用

从表9-1中可以看到,grep支持基本的正则表达式。如果使用-E(或--extended-regexp)选项,或直接使用egrep命令,就能够使用扩展的正则表达式。大多数情况下,这或许就是你要做的,除非要执行的搜索非常简单。其他两个选择更复杂些:grep-F(或--fixed-strings)选项,或者直接用fgrep命令,支持同时使用多个要匹配的搜索项;grep-P(或--perl-regexp)选项,可以让Perl编程高手使用某些Perl语言特有的正则表达式用法。

说明 在本书中,除非特别声明,使用的都是普通的grep命令和基本的正则表达式。

在继续学习之前,需要澄清几点可能造成混淆的地方。如果你对这些内容还有任何不清楚的地方,请使用列出的相关资源作为进一步学习的起点。

通配符不等于正则表达式。虽然通配符和正则表达式都使用星号(*)字符,但它们的含义完全不同。通配符中使用特定的字符(如?*)表示替换(substitution),而正则表达式中同样的字符表示要对前面的内容进行匹配的次数。例如,在通配符中,可以把c?t中的?替换成另一个字符,而且只能替换一次,所以这个通配符可以匹配catcotcut,但不能与ct匹配。在正则表达式中,c[a-z]?t中的?表示从字母A到Z都可以匹配,但只可以匹配0次或1次,所以这个正则表达式可以匹配catcotcut,也可以匹配ct

提示 有关通配符和正则表达式之间区别的更多信息,可以看看“What Is a Regular Expression”(http://docs.kde.org/stable/en/kdeutils/KRegExpEditor/whatIsARegExp.html),“Regular Expressions Explained”(www.castaglia.org/proftpd/doc/contrib/regexp.html),以及“Wildcards Gone Wild”(www.linux-mag.com/2003-12/power_01.html)。

另一个有关grep潜在的容易混淆的问题是,需要识别grep正则表达式中使用的特殊字符。例如,在正则表达式中,字符串[a-e]表示要匹配一个范围,这意味着它可以匹配abcde之间的任意字符。当在grep中使用[]这两个字符时,你需要让shell明确地知道[]字符是作为正则表达式中范围定义的分隔符,还是要搜索的词语中的一部分。需要注意的特殊符号有以下几个:

  1. . ? [ ] ^ $ | \

最后,正则表达式中的单引号和双引号的用法也有很大区别。单引号('')是在告诉shell正在搜索一个字符串,而双引号("")则是让shell知道想要使用shell变量。例如,用grep和正则表达式,按照以下方式在一个朋友的诗歌中搜索所有用到的“hey you!”这个短语的地方,就没有成功。

  1. $ grep hey you! *
  2. grep: you!: No such file or directory
  3. txt/pvzm/8 hours a day.txt:hey you! let's run!
  4. txt/pvzm/friends & family.txt:in patience they wait
  5. txt/pvzm/speed of morning.txt:they say the force

原因是只写了“hey you!”,没有用任何引号把搜索内容括起来,grep就不能明白你的意图了。它首先试图在名为“you!”的文件中搜索“hey”这个词,但没有成功,因为根本就不存在这个文件。然后它在当前工作目录中的每个文件(按照通配符*的指示)中搜索“hey”,结果找到三个匹配的内容。三个搜索结果中的第一个确实包含你要找的那个短语,所以从这一点来看,搜索好像是有效的,但事实并非如此。这种搜索很原始,并不总能表达你想要的结果。让我们再试一次。

这次用双引号将搜索内容括起来,就能修复原来不用任何引号而带来的问题了。

  1. $ grep "hey you!" *
  2. bash: !" *: event not found

这次更糟糕了! 实际上,双引号也会导致出现大问题,甚至产生比刚才看到的问题还要糟糕。怎么回事呢?叹号(!)是一个shell命令,用于引用命令历史。通常是在叹号(!)后面跟上一个PID(process ID,进程的ID)号,代表先前你运行过的命令,如!264

所以在这里,bash看到叹号(!)后,就查找跟在它后面的PID,然后报错,说它找不到先前运行过的名为" *(一个双引号、一个空格和一个星号)的命令,这真是个不可思议的命令。

事实表明,引号表示你正在搜索内容中使用shell变量,其实这根本不是你想要的效果。所以,在这儿也不能直接用双引号。试试单引号,如下所示:

  1. $ grep 'hey!' *
  2. txt/pvzm/8 hours a day.txt:hey you! let's run!

结果好多了。单引号告诉grep搜索内容不包含任何shell变量,只是一串需要匹配的字符。你瞧,现在只有一个结果,正是你要找的那个。

从这些例子中获得了哪些收获呢?什么时候应该使用单引号,什么时候应该使用双引号,什么时候任何引号都不使用,现在应该清楚了。如果要搜索精确的匹配结果,就使用单引号;如果要把shell变量结合到搜索内容中(很少有这样的需要),就使用双引号;但如果搜索关键字只包含数字和字母,完全不使用任何引号也没有问题。如果想要安全些,放心地使用单引号吧,即使只有一个词,也可以加上单引号,这没什么损害。