正则表达式
1. 什么是正则表达式
简单地说,正则表达式是一种符号表示法,用于识别文本模式。在某种程度上,它们类似于匹配文件和路径名时使用的shell通配符,但其用途更广泛。许多命令行工具和大多数编程语言都支持正则表达式,以此来解决文本操作方面的问题。然而,在不同的工具,以及不同的编程语言之间,正则表达式都会略有不同,这让事情进一步麻烦起来。方便起见,我们将正则表达式的讨论限定在POSIX标准中(它涵盖了大多数命令行工具),与许多编程语言(最著名的Perl)不同,这些编程语言使用的符号集要更多一些。
2. 正则表达式分类
正则表达式:REGEXP,REGular EXPression。
正则表达式分为两类:
- Basic REGEXP(基本正则表达式)
- Extended REGEXP(扩展正则表达式)
3. 基本正则表达式
[root@redhat ~]# ls
1 2 4 6 8 a b c d e f g h i j k l m n o p q r s t u v w x y z
10 3 5 7 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
//元字符
. //任意单个字符
[] //匹配指定范围内的任意单个字符
[^] //匹配指定范围外的任意单个字符(取反)
效果测试:
[root@redhat ~]# ls | grep '[adf]' //不能用,或空格分隔字符,不然会把,和空格当做单个字符进行匹配
a
d
f
[root@redhat ~]# ls | grep '[^adf]' //加上^则取出除adf这三个单个字符以外任意的单个字符
[root@redhat ~]# ls | grep '[a-z]' //将a到z全部匹配出来,若换成[a-Z]则是从小写a匹配到大写Z
a
b
c
d
e
f
g
省略. . .
t
u
v
w
x
y
z
[root@redhat ~]# ls | grep '[^a-Z]' //取除了从小写a到大写Z以外的任意单个字符
1
10
2
3
4
5
6
7
8
9
补充:转义“ \ ”
在正则表达式中有些许字符我们不想让他呈现他原本的含义,例如“|”是管道符,将前面命令所执行出来的结果作为后面命令的参数,“^”是指定开头的字符,“$”是指定结尾的字符,而当我们在某些情况下使用到这些字符的时候,他所呈现的结果不是我们想用到的,这种时候就需要我们将他的含义转变,也就是转义(改变他表达的含义),需要我么用到“ \ ”。
效果测试:
[root@redhat ~]# ls
'^' 2 5 8 A B d E g H j K m N p Q s T v W y Z
1 3 6 9 abc c D f G i J l M o P r S u V x Y
10 4 7 a b C e F h I k L n O q R t U w X z
此时我们想要匹配到“^”这个字符,正常情况下我们会进行如下操作:
[root@redhat ~]# ls | grep '[^]'
grep: Unmatched [, [^, [:, [., or [= //显示我们语法有问题,这里他就是把“^”当做指定开头的字符了
我们的正确做法则是应该改变他的原本含义,如下:
[root@redhat ~]# ls | grep '[\^]' //将“^”转义,让他就表示“^”这个字符
^
同时我们也可以在多个匹配中将“^”放在后面,如下:
[root@redhat ~]# ls | grep '[235^]'
^
2
3
5
[root@redhat ~]# ls
'^' 11 3 5 8 A B d E g H j K m N p Q s T v W y Z
1 2 33 6 9 abc c D f G i J l M o P r S u V x Y
10 22 4 7 a b C e F h I k L n O q R t U w X z
//匹配次数(贪婪模式)
* //匹配其前面的任意单个字符任意次
.* //任意长度的任意字符,正则表达式中的.*就是通配符里面的*
\? //匹配其前面的任意单个字符1次或0次
\+ //匹配其前面的任意单个字符至少1次
\{m,n\} //匹配其前面的任意单个字符至少m次,至多n次
效果测试:
[root@redhat ~]# ls | grep '^[123]*$'
1
11
2
22
3
33
[root@redhat ~]# ls | grep '^1\?$' //匹配前面一个字符0次或1次,0次就代表没有,所以只有1次
1
[root@redhat ~]# ls | grep '^11\?$'
1
11
[root@redhat ~]# touch ba baa baaa
[root@redhat ~]# ls | grep '^ba\+$' //匹配前面一个字符,最少一次
ba
baa
baaa
[root@redhat ~]# ls | grep '^ba\{2,3\}$' //匹配前面的单个字符最少2次,最多3次
baa
baaa
//位置锚定
^ //锚定行首,此字符后面的任意单个字符必须出现在行首
$ //锚定行尾,此字符前面的任意单个字符必须出现在行尾
^$ //空白行
\<或\b //锚定词首,其后面的任意单个字符必须作为单词首部出现
\>或\b //锚定词尾,其前面的任意单个字符必须作为单词尾部出现
/分组
\(\)
例:\(ab\)*
//后向引用
\1 //引用第一个左括号以及与之对应的右括号所包括的所有内容
\2 //引用第二个左括号以及与之对应的右括号所包括的所有内容
效果如下:
[root@redhat ~]# vim abc
[root@redhat ~]# cat abc
hello 0714-2564851 hehe
(+86)13872364194
1514864158
//此处有一行空白行
[root@redhat ~]# cat abc | grep '^$' //匹配空白行
[root@redhat ~]# cat abc | grep -v '^$' //通过-v选项取反,取出不是空白行的行
hello 0714-2564851 hehe
(+86)13872364194
1514864158
[root@redhat ~]# echo "hello world hello1 tom hello zhangsan" | grep ' \<hello\>'
hello world hello1 tom hello zhangsan //hello1中确实是hello开头,但不是hello结尾,所以只会匹配到第三个hello
[root@redhat ~]# touch ab abab ababab abababab
[root@redhat ~]# ls |grep '^\(ab\)\?$'
ab
[root@redhat ~]# ls |grep '^\(ab\)\1$' //引用第一个左括号以及与之对应的右括号所包括的所有内容
abab
[root@redhat ~]# touch abdcdc
[root@redhat ~]# ls |grep '^\(ab\)\(dc\)\2$' //引用第二个左括号以及与之对应的右括号所包括的所有内容
abdcdc
扩展内容:
将 hello world ftx 中的world 和 ftx互换一下位置
[root@redhat ~]# echo 'hello world ftx' | sed 's/hello \(.*\) \(.*\)/hello \2 \1/g'
hello ftx world //.*是前面说过的匹配任意长度的字符,\2引用前面第2个括号的内容,\1引用前面第1个括号的内容
4. 扩展正则表达式
//字符匹配
. //匹配任意单个字符
[] //匹配指定范围内的任意单个字符
[^] //匹配指定范围外的任意单个字符
//次数匹配
* //匹配其前面的任意单个字符任意次
? //匹配其前面的任意单个字符1次或0次
+ //匹配其前面的任意单个字符至少1次
{m,n} //匹配其前面的任意单个字符至少m次,至多n次
//位置锚定
^ //锚定行首,此字符后面的任意单个字符必须出现在行首
$ //锚定行尾,此字符前面的任意单个字符必须出现在行尾
^$ //空白行
\<或\b //锚定词首,其后面的任意单个字符必须作为单词首部出现
\>或\b //锚定词尾,其前面的任意单个字符必须作为单词尾部出现
//分组
() //分组
\1,\2,\3,....
例:(ab)*
//后向引用
\1 //引用第一个左括号以及与之对应的右括号所包括的所有内容
\2 //引用第二个左括号以及与之对应的右括号所包括的所有内容
//或者
| //or 默认匹配|的整个左侧或者整个右侧的内容
//例:C|cat表示C或者cat,要想表示Cat或者cat则需要使用分组,如(C|c)at
5.扩展正则表达式的优点
扩展正则表达式相对于基本正则优化了一些命令语法问题,使正则表达式更加简洁、易懂。
//加上-E选项,使用扩展正则表达式
效果如下:
[root@redhat ~]# echo 'hello world ftx' | sed -E 's/hello (.*) (.*)/hello \2 \1/g'
hello ftx world
[root@redhat ~]# ls |grep -E '^(ab)(dc)\2$'
abdcdc
由上述两个执行出来的效果可以看出,扩展正则表达式基本上和基本正则表达式相同,这是因为扩展正则表达式是基于基本正则表达式优化之后的,最容易看出的点就是我们不再需要使用转义“^”来改变他的含义
[root@redhat ~]# touch c cat
[root@redhat ~]# ls | grep '^c\|cat$'
c
cat
[root@redhat ~]# ls | grep -E '^c|cat$'
c
cat
[root@redhat ~]# touch Cat cat
[root@redhat ~]# ls | grep '^Cat\|cat'
cat
Cat
[root@redhat ~]# ls | grep -E '^Cat|cat'
cat
Cat
[root@redhat ~]# ls | grep -E '^(C|c)at'
cat
Cat
使用场景
在我们工作中,分有可能会出现让你在一些文本内容中提取一些有用的信息,这种时候我们就需要用到正则表达式去将他匹配提取出来。
提取abc文件中的座机号码
[root@redhat ~]# cat abc
hello 0714-2564851 hehe
(+86)13872364194
15148641584
(0235)5766813
12345678910
[root@redhat ~]# cat abc | grep -E '\(?0[0-9]{3}\)?-?[0-9]{7}'
hello 0714-2564851 hehe
(0235)5766813
思路分析:
'\(?0[0-9]{3}\)?-?[0-9]{7}'
\(? 是将(转义成为单纯的一个符号,加上?是因为他可能没有(也可能有(,所以我们将他匹配0次或1次
0[0-9]{3} 中第一个0是因为座机号一般开头都为0,所以我们可以直接确认,[0-9]是匹配单个字符从0-9,{3}则是前面的匹配的次数,此处为3次,因为座机中可能会出现-,而-前一般都是4个数字,在我们确定了第一个数字为0后,我们就只需要匹配后面三个数字
-? 就是匹配 “-” 0次或1次,因为-可能有可能没有
[0-9]{7}则是匹配后面的7位数字
//由此就可以匹配出我们想要的结果
//理解各个正则表达式的用法之后我们就可以很轻易的完成查询工作,手机号码的提取亦是如此:
同样是abc这个文件中的内容
[root@redhat ~]# cat abc
hello 0714-2564851 hehe
(+86)13872364194
15148641584
(0235)5766813
12345678910
//同样的思路,但是在日常生活中我们需要有一定的常识,我们都知道各大运营商的手机号开头都是有规定的,比方说138,155,177,183,191,国内手机号首位一定1,而在第二位就有了要求,现在手机号的第二位只有3,5,7,8,9,所以我们在匹配时需要考虑到这一点,某些情况下还需要将手机号前的国际区号(+86)这类信息提取出来,整理完思绪后得出的结果如下:
[root@redhat ~]# cat abc | grep -E '(\(\+86\))?1[35789][0-9]{9}'
(+86)13872364194
15148641584
如果没有上述的基本常识,则会出现一下情况:
[root@redhat ~]# cat abc | grep -E '(\(\+86\))?1[0-9]{10}'
(+86)13872364194
15148641584
12345678910 //很显然,这行不为手机号的行也匹配出来了