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
中的?
替换成另一个字符,而且只能替换一次,所以这个通配符可以匹配cat
、cot
和cut
,但不能与ct
匹配。在正则表达式中,c[a-z]?t
中的?
表示从字母A到Z都可以匹配,但只可以匹配0次或1次,所以这个正则表达式可以匹配cat
、cot
、cut
,也可以匹配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]
表示要匹配一个范围,这意味着它可以匹配a
、b
、c
、d
或e
之间的任意字符。当在grep
中使用[
和]
这两个字符时,你需要让shell明确地知道[
和]
字符是作为正则表达式中范围定义的分隔符,还是要搜索的词语中的一部分。需要注意的特殊符号有以下几个:
. ? [ ] ^ $ | \
最后,正则表达式中的单引号和双引号的用法也有很大区别。单引号('
和'
)是在告诉shell正在搜索一个字符串,而双引号("
和"
)则是让shell知道想要使用shell变量。例如,用grep
和正则表达式,按照以下方式在一个朋友的诗歌中搜索所有用到的“hey you!”这个短语的地方,就没有成功。
$ grep hey you! *
grep: you!: No such file or directory
txt/pvzm/8 hours a day.txt:hey you! let's run!
txt/pvzm/friends & family.txt:in patience they wait
txt/pvzm/speed of morning.txt:they say the force
原因是只写了“hey you!”,没有用任何引号把搜索内容括起来,grep
就不能明白你的意图了。它首先试图在名为“you!”的文件中搜索“hey”这个词,但没有成功,因为根本就不存在这个文件。然后它在当前工作目录中的每个文件(按照通配符*
的指示)中搜索“hey”,结果找到三个匹配的内容。三个搜索结果中的第一个确实包含你要找的那个短语,所以从这一点来看,搜索好像是有效的,但事实并非如此。这种搜索很原始,并不总能表达你想要的结果。让我们再试一次。
这次用双引号将搜索内容括起来,就能修复原来不用任何引号而带来的问题了。
$ grep "hey you!" *
bash: !" *: event not found
这次更糟糕了! 实际上,双引号也会导致出现大问题,甚至产生比刚才看到的问题还要糟糕。怎么回事呢?叹号(!
)是一个shell命令,用于引用命令历史。通常是在叹号(!
)后面跟上一个PID(process ID,进程的ID)号,代表先前你运行过的命令,如!264
。
所以在这里,bash看到叹号(!
)后,就查找跟在它后面的PID,然后报错,说它找不到先前运行过的名为" *
(一个双引号、一个空格和一个星号)的命令,这真是个不可思议的命令。
事实表明,引号表示你正在搜索内容中使用shell变量,其实这根本不是你想要的效果。所以,在这儿也不能直接用双引号。试试单引号,如下所示:
$ grep 'hey!' *
txt/pvzm/8 hours a day.txt:hey you! let's run!
结果好多了。单引号告诉grep
搜索内容不包含任何shell变量,只是一串需要匹配的字符。你瞧,现在只有一个结果,正是你要找的那个。
从这些例子中获得了哪些收获呢?什么时候应该使用单引号,什么时候应该使用双引号,什么时候任何引号都不使用,现在应该清楚了。如果要搜索精确的匹配结果,就使用单引号;如果要把shell变量结合到搜索内容中(很少有这样的需要),就使用双引号;但如果搜索关键字只包含数字和字母,完全不使用任何引号也没有问题。如果想要安全些,放心地使用单引号吧,即使只有一个词,也可以加上单引号,这没什么损害。