之前有写过两篇关于 sed awk 的文章,但是内容太过简单了。近期打算就这方面话题写一个系列,系统介绍一些与 shell script 有关的内容。
因为即将介绍的一系列内容都将与正则表达式有关联,所以作为这个系列的开篇之作,先详细介绍一下正则表达式的使用。关于正则表达式的文章相信已经数不胜数了,我之所以再写一篇,一方面结合自己学习的心得用自己的话来组织一下,希望对刚接触正则的朋友有所帮助;另一方面也是自己再做个总结写个笔记加深理解。
进入正题吧。首先要解决的一系列问题是:正则是什么,哪些场合用到正则,正则能解决什么问题?(注:我下面要给出的一系列定义都未必是各大书籍的经典定义,而是我自己理解自己组织的语言)
正则表达式描述了特殊的字符序列,也就是,正则表达式的结果就是一系列字符,不论它有多复杂或是多简单。我们接下来要讨论的正则表达式(或称 Regular Expression ,简称 RE )都是指 linux/unix 系统中 grep ed sed 工具中所使用的 RE (使用 RE 的并非只有这些工具,例如 php perl 都使用 RE ,但是他们不在我们讨论的范围之内)。为什么不提 egrep awk 呢?因为他们使用的 RE grep sed 所使用的有些很小的区别,他们将在本文的后半部分提到。
还是举点例子吧,晦涩的文字太不直观了!我们下面的大多数例子都将以 grep 工具来讲,因为 grep linux/unix 系统最常用最简单而且要使用 RE 的工具!既然要拿 grep 来举例,还是先介绍一下 grep 吧。 grep 命令的作用是在一个或多个文件中搜索特定的字符串,语法为:
grep pattern file(s)
每个文件中符合模式 pattern 的字符串所在的行都将显示在终端上。(注意,这里的 pattern 将广泛使用在 grep sed awk 中,事实上 RE 就是使用在 pattern 中的!)最简单的一个例子 :
grep ‘root’ /etc/passwd
上面这个 grep 命令中, root 就是一个最简单的 RE !整条命令的意思就是:在文件 /etc/passwd 中搜索包含字符串 root 的行,并把它(们)显示出来。(这里的单引号 ’’ 要讨论起来过于复杂,我们将在其他的文章中重点讨论,记得先这么用吧 grep ‘xxxx’ file
为什么 root 也是一个 RE 呢?别把 RE 看得过于复杂,在不使用特殊字符(在 RE 中称为元字符)的情况下, RE 就是一个一个的字符而已!记住: RE 都只是处理单个字符的! root 这个 pattern 到底是怎么工作的呢?很简单,就是在每一行先找 r 字符,如果找到了,再依次找 oot 。换句话说,就是要找到 r 后面紧跟着 o 再紧跟着 o 再紧跟着 t 的,才把该行显示出来。再复杂的 RE 也都是这样工作的!那 RE 岂不是很简单?它难在哪里呢?难就难在它最强大的地方,模糊匹配,也就是下面要讲的元字符。(如果没有元字符,都像 ’root’ 这样,也就没必要搞什么 RE 了, pattern 里写个单词不就得了)
重点难点的元字符来了啊!很多书和文章都会一鼓脑儿把元字符列一张表,让学习者去理解或者背下来。这样的效果是,那张表不结合实例看三遍以上是理解不了的!结合我以前学习的心得,我把元字符分成 2 类来介绍。其实元字符总共也没几个,分类是为了便于理解。
第一类:可以单独使用的元字符。这类字符有 ( 下面列出来的字符都请一个一个的看,不要连起来,因为他们都是独立存在就有意义的 )
.  ^  $<?XML:NAMESPACE PREFIX = O />

. 代表任意单个字符
^ 作为 RE 的第一个字符,代表行的开始;在其他位置是普通字符
$ 作为 RE 的最后一个字符,代表行的结束;在其他位置是普通字符
举例:

 

 

$cat file

11111.1
2222^2
3333$3
                  注意这是一个空行
$grep ‘.’ file

11111.1
2222^2
3333$3
为什么会显示三行呢?因为命令中的 . 是元字符,而不是普通的点。元字符 . 代表任何单个字符,所以只要有字符的行,这条 grep 命令都会把它显示出来。
$grep ‘^’ file

11111.1
2222^2
3333$3
                  注意这里的空行也显示了
为什么没有 ^ 符号的行也显示呢?因为这个 RE ^ 是在第一个字符位置,这里的 ^ 代表行首,只要有行首的行都显示(任何行哪怕是空行都有行首和行尾)同样的道理 grep ‘$’ file 也会显示所有行。
$grep ‘.^’ file

2222^2
这个命令中的 ^ 前面有个元字符,所以这里的 ^ 不表示行首,而是个普通字符; .^ 表示一个任意字符后面紧跟字符 ^ ,这里匹配的内容就是 2^ ,于是这一行被显示。
$grep ‘$.’ file

3333$3
这个命令中的 $ 后面有一个元字符,所以不表示行尾, $. 匹配的是 $3 ,于是该行被显示。
$grep ‘^$’ file

                  这里显示了一个空行
^$ 匹配行首和行尾之间没有字符的行,也即是空行了。 ^$ 表示空行,而 ^ $ (中间有个空格哦)表示只有一个空格的行。
好了,第一类元字符介绍完了,简单吧,只有三个而已。
第二类,用来修饰其他字符(普通字符或第一类元字符)的元字符,这一类元字符要多一些,一个一个来吧。
常用的匹配单个字符的还有 [] […] 匹配括号中的字符之一。下面列举一些常见的使用方式:
[abc]      匹配单个字符 a b c
[123]          匹配单个字符 1 2 3
[a-z]           匹配小写字母 a-z 之一

[a-zA-Z]     匹配任意英文字母之一

[0<?XML:NAMESPACE PREFIX = ST1 />-9a-zA-Z] 匹配任意英文字母或数字之一

注意上面标红色的单个和之一了吧,不管 [] 里面多复杂,它的结果都是一个字符!
[] 里面的字符值得注意的有几个特例:
-   ^  [  ]       (也请分开一个一个看)
- 在上面的例子已经提到了,表示范围。但是注意:如果 - 符号出现在 [] 里面的首尾位置是普通字符。 例如:
[-abc-]        匹配字符 -abc 之一
- 符号虽然出现了 2 次,这里和一个 - 是同样的意义,试想 aa 之一和 aaa 之一有区别吗?

^ 符号如果出现在 [] 的起始位置表示否定,但是在其他位置是普通字符
[^ab^c] 匹配不是 a b ^ c 的任意字符
$cat file
Aaaaabc
Bbbbbbc^
cccccbc33
$grep ‘[^ab^c]’ file
cccccbc33
只有第三行的 3 满足了匹配要求,所以只显示这一行。
$grep ‘[x^]’ file
Bbbbbbc^
[x^] 匹配字符 x ^
如果 [] 中要包含字符 [ ] 该怎么办呢, [][] 应该怎么理解呢? [[]] 又怎么理解呢?有一个原则:字符 ] [ 放在 [] 中,只有紧跟在 [ 后面的 ] 和紧贴于 ] 前面的 [ 是普通字符。还有一个更好理解的方式,就是用反斜杠转义 [] 中的 [ ] ,如 [\[\]], 当然这看起来同样不那么舒服。
除了 [] 之外,还有很多第二类元字符,先来整体认识一下他们吧(这次不是一个一个看了,以空格为分界来分段看吧)
*   \    \?      \+    \{n,m\}
* 用于修饰前导字符,表示前导字符出现任意多次
\? 用于修饰前导字符,表示前导字符出现 0 1
\+ 用于修饰前导字符,表示前导字符出现 1 或多次
\{n,m\}  用于修饰前导字符,表示前导字符出现 n m n m 都是整数,且 n<m
用于转义紧跟其后的单个特殊字符,使该特殊字符成为普通字符
注:以上“前导字符”表示紧贴于元字符前面的单个普通字符或第一类元字符。举例吧:
a* 匹配连续的任意(也包括 0 )个 a
.* 匹配连续的任意(也包括 0 )个任意字符,传说中的万能匹配!
a\? 匹配 0 1 a
a\+ 匹配 1 或多个 a
a\{3,5\} 匹配 3 5 个连续的 a
\.* 匹配 0 或多个连续的 .  \. 表示普通字符句点 .
注意到没有, *  \?  \+  \{n,m\} 这几种元字符完成类似的功能,都是匹配连续出现的前导字符,只是出现的次数不一样罢了,不是很难吧。
\{n,m\} 还有其他几种形式:
\{n\}  连续的 n 个前导字符
\{n,\}  连续的至少 n 个前导字符
发现没有, \? 等价于 \{0,1\}  * 等价于 \{0,\}   \+ 等价于 \{1,\}
特别提一下,你可能会在很多地方看到 ? +, 而不是这里说的 \? \+ 。这个问题我在开篇有所提及。 grep/sed 用的是 \? \+ ,普通正则 RE egrep/awk 用的是 ? + ,扩展正则 ERE 。仅这点区别而已, RE ERE 大部分是相同的。
还有一组元字符也是常用的,但是相对比较难理解,拿到这里单独说一下:
\(  \)
这里的 \( \) 一定是成对出现的 , 这对带反斜杠的小括号的意义是:将小括号中匹配的字符串存储到下一个寄存器中 (1-9) \( \) sed s/// 替换中经常使用,其实它并不限于应用在 sed 中,举例:
^\(.\)  行中第一个字符存到 1 号寄存器中(为什么是行中第一个字符?回上面复习一下吧
^\(.\)\1 行首两个字符,且他们相同
^\(.\).*\1$ 行首尾两个字符相同
\1 的意思就是取 1 号寄存器的内容,这是固定格式 \n 1 n 9
$cat file
aaaaabc
bc^b
1234
gdfdd
$ grep '^\(.\)\1' file
aaaabc
$ grep '^\(.\).*\1$' file
bc^b
正则表达式理论方面的就介绍到这里了,下面再补充一些更常用也更长一点的 RE 例子。
[0-9a-z?,.;:’”]
这个表达式将匹配“任意单个字符,可以是数字、小写字母、问好、逗号、句号、分号、冒号、单引号或双引号
[0-9]\{2\} 连续的两位数字
[0-1][0-9][-/][0-3][0-9][-/][0-9]\{2\}
这个表达式能够用来表示时间格式 MM-DD-YY MM/DD/YY, 方括号 [] 中的 - 放在第一个位置,确保它在 [] 中不被解释为范围,而是普通字符 -
还有一点要注意的,就是 \{2\} 仅是用来修饰前面的单个字符的,别忘了 [0-9] 也是单个字符
gep ‘<.*>’ file  将匹配所有带有 <> 标记的行
can[ no’]*t 将匹配 cant can t cannt cannot cann’t cannnnnnt 等等。。。
80[234]\ ?86 将匹配 8086 80286 80386 80486
^$ 匹配空行
^.*$ .* 都匹配任意行(包括空行)
[0-9][0-9]*\.\{5,\}[0-9][0-9]*
匹配“至少一位数字紧跟至少 5 个句点紧跟至少一位数字。这个正则应该分段来看:
[0-9] 是一位数字; [0-9]* 0 或多位数字; [0-9][0-9]* 就是“至少一位数字”,相当于 [0-9]\+
\.\{5,\} 中的 . 被转义了,表示普通的 于是 \.\{5,\} 就表示 至少 5 个句点
先举这些例子吧,更多的例子以后再更新了