shell编程基础(五): 正则表达式及其使用
正则表达式
1、前情提要
以前我们用grep在一个文件中找出包含某些字符串的行,比如在头文件中找出一个宏定义。其实grep还可以找出符合某个模式(Pattern)的一类字符串。例如找出所有符合xxxxx@xxxx.xxx模式的字符串(也就是email地址),要求x字符可以是字母、数字、下划线、小数点或减号,email地址的每一部分可以有一个或多个x字符,例如abc.d@ef.com、1_2@987-6.54,当然符合这个模式的不全是合法的email地址,但至少可以做一次初步筛选,筛掉a.b、c@d等肯定不是email地址的字符串。再比如,找出所有符合yyy.yyy.yyy.yyy模式的字符串(也就是IP地址),要求y是0-9的数字,IP地址的每一部分可以有1-3个y字符。
如果要用grep查找一个模式,如何表示这个模式,这一类字符串,而不是一个特定的字符串呢?从这两个简单的例子可以看出,要表示一个模式至少应该包含以下信息:
字符类(Character Class):如上例的x和y,它们在模式中表示一个字符,但是取值范围是一类字符中的任意一个。
数量限定符(Quantifier): 邮件地址的每一部分可以有一个或多个x字符,IP地址的每一部分可以有1-3个y字符
各种字符类以及普通字符之间的位置关系:例如邮件地址分三部分,用普通字符@和.隔开,IP地址分四部分,用.隔开,每一部分都可以用字符类和数量限定符描述。
为了表示位置关系,还有位置限定符(Anchor)的概念,将在下面介绍。
规定一些特殊语法表示字符类、数量限定符和位置关系,然后用这些特殊语法和普通字符一起表示一个模式,这就是正则表达式(Regular Expression)。
例如email地址的正则表达式可以写成[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+.[a-zA-Z0-9_.-]+,IP地址的正则表达式可以写成[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}。
等下介绍正则表达式的语法,我们先看看正则表达式在grep中怎么用。例如有这样一个文本文件testgrep:
[root@VM_0_5_centos test]# vi testgrep.file [root@VM_0_5_centos test]# cat testgrep.file 192.168.13.108 123.5254.045.678 abcde52s 198.23.233.342 1233.232.232.4
查找其中包含IP地址的行:
注:1、egrep相当于grep -E,表示采用Extended正则表达式语法。另外还有fgrep命令,相当于grep -F,表示只搜索固定字符串而不搜索正则表达式模式,不会按正则表达式的语法解释后面的参数。
2、正则表达式参数用单引号括起来了,因为正则表达式中用到的很多特殊字符在Shell中也有特殊含义(例如\),只有用单引号括起来才能保证这些字符原封不动地传给grep命令,而不会被Shell解释掉。
grep的正则表达式有Basic和Extended两种规范: 1、grep正则表达式的Extended规范在基本语法中介绍; 2、Basic规范也有Extended规范的这些语法,只是字符?+{}|()应解释为普通字符,要表示上述特殊含义则需要加\转义。如果用grep而不是egrep,并且不加-E参数,则应该遵照Basic规范来写正则表达式。
问:192.168.13.108符合上述模式,由三个.隔开的四段组成,每段都是1到3个数字,所以这一行被找出来了,可为什么1233.232.232.4也被找出来了呢?
答:因为grep找的是包含某一模式的行,这一行包含一个符合模式的字符串233.232.232.4。相反,123.5254.045.678这一行不包含符合模式的字符串,所以不会被找出来。
grep是一种查找过滤工具,正则表达式在grep中用来查找符合模式的字符串。其实正则表达式还有一个重要的应用是验证用户输入是否合法,例如用户通过网页表单提交自己的email地址,就需要用程序验证一下是不是合法的email地址,这个工作可以在网页的Javascript中做,也可以在网站后台的程序中做,例如PHP、Perl、Python、Ruby、Java或C,所有这些语言都支持正则表达式,可以说,目前不支持正则表达式的编程语言实在很少见。除了编程语言之外,很多UNIX命令和工具也都支持正则表达式,例如grep、vi、sed、awk、emacs等等。“正则表达式”就像“变量”一样,它是一个广泛的概念,而不是某一种工具或编程语言的特性。
2、基本语法
我们知道C的变量和Shell脚本变量的定义和使用方法很不相同,表达能力也不相同,C的变量有各种类型,而Shell脚本变量都是字符串。同样道理,各种工具和编程语言所使用的正则表达式规范的语法并不相同,表达能力也各不相同,有的正则表达式规范引入很多扩展,能表达更复杂的模式,但各种正则表达式规范的基本概念都是相通的。本节介绍egrep(1)所使用的正则表达式,它大致上符合POSIX正则表达式规范,详见regex(7)(看这个man page对你的英文绝对是很好的锻炼)。希望读者仿照上一节的例子,一边学习语法,一边用egrep命令做实验。
字符类:
字符 | 含义 | 举例 |
. | 匹配任意一个字符 | abc.可以匹配abcd、abc9等 |
[] | 匹配括号中的任意一个字符 | [abc]d可以匹配ad、bd或cd |
- | 在[]括号内表示字符范围 | [0-9a-fA-F]可以匹配一位十六进制数字 |
^ | 位于[]括号内的开头,匹配除括号中的字符之外的任意一个字符 | [^xy]匹配除xy之外的任一字符,因此[^xy]1可以匹配a1、b1但不匹配x1、y1 |
[[:xxx:]] | grep工具预定义的一些命名字符类 | [[:alpha:]]匹配一个字母,[[:digit:]]匹配一个数字 |
数量限定符:
字符 | 含义 | 举例 |
? |
紧跟在它前面的单元应匹配零次或一次 |
[0-9]?\.[0-9]匹配0.0、2.3、.5等,由于.在正则表达式中是一个特殊字符,所以需要用\转义一下,取字面值 |
+ |
紧跟在它前面的单元应匹配一次或多次 |
[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+匹配email地址 |
* |
紧跟在它前面的单元应匹配零次或多次 |
[0-9][0-9]*匹配至少一位数字,等价于[0-9]+,[a-zA-Z_]+[a-zA-Z_0-9]*匹配C语言的标识符 |
{N} |
紧跟在它前面的单元应精确匹配N次 |
[1-9][0-9]{2}匹配从100到999的整数 |
{n,} |
紧跟在它前面的单元应匹配至少N次 |
[1-9][0-9]{2,}匹配三位以上(含三位)的整数 |
{,M} |
紧跟在它前面的单元应匹配最多M次 |
[0-9]{,1}相当于[0-9]? |
{N,M} |
紧跟在它前面的单元应匹配至少N次,最多M次 |
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}匹配IP地址 |
再次注意grep找的是包含某一模式的行,而不是完全匹配某一模式的行。再举个例子,如果文本文件的内容是:
[root@VM_0_5_centos test]# vi testfile.txt
[root@VM_0_5_centos test]# cat testfile.txt
acaabc
caad
efg
sdcasd
sda
查找a*这个模式的结果是三行都被找出来了:
注:a匹配0个或多个a,而第三行包含0个a,所以也包含了这一模式。单独用a这样的正则表达式做查找没什么意义,一般是把a*作为正则表达式的一部分来用。
位置限定符:
字符 | 含义 | 举例 |
^ |
匹配行首的位置 |
^Content匹配位于一行开头的Content |
$ |
匹配行末的位置 |
;$匹配位于一行结尾的;号,^$匹配空行 |
\< |
匹配单词开头的位置 |
\<th匹配... this,但不匹配ethernet、tenth |
\> |
匹配单词结尾的位置 |
p\>匹配leap ...,但不匹配parent、sleepy |
\b |
匹配单词开头或结尾的位置 |
\bat\b匹配... at ...,但不匹配cat、atexit、batch |
\B |
匹配非单词开头和结尾的位置 |
\Bat\B匹配battery,但不匹配... attend、hat ... |
位置限定符可以帮助grep更准确地查找,例如上一节我们用[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}查找IP地址,找到这两行:
192.168.13.108 198.23.233.342 1233.232.232.4
如果用^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$查找,就可以把1233.232.232.4这一行过滤掉了。
其它特殊字符:
字符 | 含义 | 举例 |
\ |
转义字符,普通字符转义为特殊字符,特殊字符转义为普通字符 |
普通字符<写成\<表示单词开头的位置,特殊字符.写成\.以及\写成\\就当作普通字符来匹配 |
() |
将正则表达式的一部分括起来组成一个单元,可以对整个单元使用数量限定符 |
([0-9]{1,3}\.){3}[0-9]{1,3}匹配IP地址 |
| |
连接两个子表达式,表示或的关系 |
n(o|either)匹配no或neither |
以上介绍的是grep正则表达式的Extended规范,Basic规范也有这些语法,只是字符?+{}|()应解释为普通字符,要表示上述特殊含义则需要加\转义。如果用grep而不是egrep,并且不加-E参数,则应该遵照Basic规范来写正则表达式。
3、grep
1.作用
Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。
rep家族包括grep、egrep和fgrep: egrep和fgrep的命令只跟grep有很小不同。 egrep是grep的扩展,支持更多的re元字符; fgrep就是fixed grep或fast grep,它们把所有的字母都看作单词,也就是说,正则表达式中的元字符表示回其自身的字面意义,不再特殊。 linux使用GNU版本的grep。它功能更强,可以通过-G、-E、-F命令行选项来使用egrep和fgrep的功能。
2.格式
grep [options]
3.主要参数
grep --help [options]主要参数: -c:只输出匹配行的计数。 -i:不区分大小写。 -h:查询多文件时不显示文件名。 -l:查询多文件时只输出包含匹配字符的文件名。 -n:显示匹配行及行号。 -s:不显示不存在或无匹配文本的错误信息。 -v:显示不包含匹配文本的所有行。 --color=auto :可以将找到的关键词部分加上颜色的显示。
pattern正则表达式主要参数:(pattern部分最好用双引号)
\ 忽略正则表达式中特殊字符的原有含义。 ^ 匹配正则表达式的开始行。 $ 匹配正则表达式的结束行。 \< 从匹配正则表达 式的行开始。 \> 到匹配正则表达式的行结束。 [ ] 单个字符,如[A]即A符合要求 。 [ - ] 范围,如[A-Z],即A、B、C一直到Z都符合要求 。 . 所有的单个字符。 * 有字符,长度可以为0。
4.grep命令使用简单实例
显示所有以d开头的文件中包含 test的行; [root@VM_0_5_centos test]# grep “test” d* 显示在aa,bb,cc文件中匹配test的行(./*表示当前目录下的所有文件); [root@VM_0_5_centos test]# grep ‘test’ aa bb cc [root@VM_0_5_centos test]# egrep "sda" ./* ./test:sda ./testfile.txt:sda ./testsd:sdadf [root@VM_0_5_centos test]# egrep "sda" ./* -n ./test:2:sda ./testfile.txt:5:sda ./testsd:2:sdadf [root@VM_0_5_centos test]# egrep "sda" ./* -c ./test:1 ./testfile.txt:1 ./testgrep.file:0 ./testsd:1 ./tfun.sh:0 显示所有包含每个字符串至少有5个连续小写字符的字符串的行; [root@VM_0_5_centos test]# grep “[a-z]\{5\}” test
[root@VM_0_5_centos test]# egrep “[a-z]{5}” test
如果asd被匹配,则s就被存储到内存中,并标记为1,然后搜索任意个字符(.*),这些字符后面紧跟着另外一个s(\1),找到就显示该行。如果用egrep或grep -E,就不用”\”号进行转义,直接写成’a(s)d.*\1′就可以了;
[root@VM_0_5_centos test]# grep “a\(s\)d.*\1” test
5.grep命令使用复杂实例
明确要求搜索子目录:
grep -r
或忽略子目录:
grep -d skip
如果有很多输出时,您可以通过管道将其转到’less’上阅读:
IP地址的正则表达式可以写成[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}。
grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' testgrep.file | less
这样,您就可以更方便地阅读。
注意:您必需提供一个文件过滤方式(搜索全部文件的话用 *)。如果您忘了,’grep’会一直等着,直到该程序被中断。如果您遇到了这样的情况,按 ,然后再试。
下面还有一些有意思的命令行参数:
grep -i pattern files //不区分大小写地搜索。默认情况区分大小写, grep -l pattern files //只列出匹配的文件名, grep -L pattern files //列出不匹配的文件名, grep -w pattern files //只匹配整个单词,而不是字符串的一部分(如匹配’magic’,而不是’magical’), grep -C number pattern files//匹配的上下文分别显示[number]行, grep pattern1 | pattern2 files //显示匹配 pattern1 或 pattern2 的行, 例如: grep "abc\|xyz" testfile 表示过滤包含abc或xyz的行 grep pattern1 files | grep pattern2 //显示既匹配 pattern1 又匹配 pattern2 的行。 grep -n pattern files //即可显示行号信息 grep -c pattern files //即可查找总行数
这里还有些用于搜索的特殊符号:
\< 和 \> 分别标注单词的开始与结尾 例如: grep man * 会匹配 ‘Batman’、’manic’、’man’等; grep ‘\<man’ * 会匹配’manic’和’man’,但不是’Batman’; grep ‘\<man\>’ 只匹配’man’,而不是’Batman’或’manic’等其他的字符串。 ‘^’ 指匹配的字符串在行首; ‘$’ 指匹配的字符串在行尾; 例如: [root@VM_0_5_centos test]# cat testfile.txt acaabc caad efg sdcasd sda [root@VM_0_5_centos test]# grep '^a' testfile.txt acaabc [root@VM_0_5_centos test]# grep -E '^a' testfile.txt acaabc [root@VM_0_5_centos test]# grep -E 'a$' testfile.txt sda