通配符和正则表达式

通配符 通配是指Shell在遇到通配符时,会自动把它们扩展成一系列匹配的文件名。无论是命令,还是脚本,只要它操作的是文件,都可以利用通配符。 shell中的通

通配符

通配是指Shell在遇到通配符时,会自动把它们扩展成一系列匹配的文件名。无论是命令,还是脚本,只要它操作的是文件,都可以利用通配符

shell中的通配符:

通配符

意义

*

任意多个(包括0个)任意字符

任意一个字符

[abc]

表示abc中任意一个字符

[a-d]

表示a到d之间的所有字符中的任意一个

[^abc]或[!abc]   [^a-d]或[!a-d]

取反,表示abc之外的所有字符

注意:

通配符只会匹配非隐藏文件,如果要匹配隐藏文件,必须先用句点.显式的指定要匹配的隐藏文件路径名称扩展可以使用上述的通配符和下面的扩展匹配

 

扩展通配符:

花括号{}                        #依次扩展花括号内的字符串

 

表示扩展为4个log文件

^   用在[ ]外表示行首

$ 表示行尾

 

正则表达式

和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求。

元字符

1\b是正则表达式规定的一个特殊代码代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。

单词的边界除了\b外,还可以使用\<  \>来匹配

实例1:

假设你在一篇英文小说里查找hi,你可以使用正则表达式hi。它可以精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。

通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配hi , HI , Hi , hI 这四种情况中的任意一种。

不幸的是,很多单词里包含hi这两个连续的字符,比如him , history , high 等等。用hi来查找的话,这里边的hi 也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b。(hi前后的\b用于匹配一个位置,单词的分隔)

实例2

假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b

 

. 是另一个元字符,匹配除了换行符以外的任意字符。

* 同样是元字符,不过它代表的不是字符,也不是位置,而是数量——指定*前边的内容可以连续重复使用任意次(0到多次)以使整个表达式得到匹配。

因此,       .*   连在一起就意味着任意数量的不包含换行的字符

 

2、\d也是元字符,匹配单个数字(0,或12…9)

如果要匹配025-23124523,就可以使用\d\d\d-\d\d\d\d\d\d\d\d

3、nm}指定匹配字符个数

{n}         精确指定匹配n个字符

{n,}        匹配至少n个字符

{,n}        匹配最多n个字符

{n,m}     匹配n到m个字符(包括两端的n和m)

上述的电话号码可使用:\d{3}-\d{8},第一位0是固定不变的,可改为:0\d{2}-\d{8}

 

 

元字符小结:

元字符

含义

.

匹配除换行符(\n)以外的任意1个字符(包括空白符)(基础)

匹配其本身?前面单个字符0或1次扩展

+

匹配其本身+前面单个字符1到无穷多次扩展

*

匹配其本身*前面单个字符0到无穷多次(基础)

\w

匹配字母、数字、下划线、汉字(一个\w匹配一个字符)

(反向:\W),这里反向的意思是指匹配匹配字母、数字、下划线、汉字之外的字符,下同

\s

匹配任意空白(一个\s匹配一个空白)

(反向:\S)

\d

匹配数字(一个\d匹配一个数字)

(反向:\D)

\b

匹配单词的边界位置(一个\b匹配一个边界位置)

(反向:\B)

^

匹配字符串的开始,或者说行首(基础)

$

匹配字符换的结尾,或者说行尾(基础)

(  )

分组(扩展

[  ]

(反向:[^  ])   (基础)

 

扩展的正则表达式:

+、?、|、{、}、(、),在基础正则表达式中使用它们需要使用反斜线\来转义

 

字符转义

如果要查找元字符本身,比如查找 . 或者 * 就会有问题:你没办法指定它们,因为它们会被解释成别的意思。此时就得使用 \ 来取消这些字符的特殊意义,使其表现为其字面意义。因此,应该使用\.\* 。当然,要查找 \ 本身,你也得用\\

 

重复——限定字符

?           重复前面的字符0到1次

+            重复前面的字符1到无穷多次

*            重复前面的字符0到无穷多次

{n}     重复前面的字符n次

  {n,}        重复前面的字符至少n次

  {,n}        匹配最多n个字符

{n,m}  重复前面的字符n到m次(包括两端)

 

[  ]的用法小结 

[abcdef]     表示abcdef中的任何一个字符

[a-zA-Z0-9]  表示从a到z、A-Z、0-9中的任何一个字符

[1-9]    表示从1到9之间的任何一个数字

注意:

[  ]内除了- 和 ^  和 ! 外(其中^和!都表示取反),其它所有字符都被视为普通字符,表现为字面意义

[root@kube-node1 ~]# cat test.txt 
101,weak1,Test1
102,weak2,Test2
103,weak3,Test3
104,weak4,Test4
[root@kube-node1 ~]# sed -r 's#([^,]*)(.*)#(\1)\2#' test.txt        #注意第一个整个匹配([^,]*)表示开头/非逗号(遇到逗号这结束匹配)
(101),weak1,Test1 (102),weak2,Test2 (103),weak3,Test3 (104),weak4,Test4

[root@kube-node1 ~]# echo "The Geek Stuff" | sed -r 's#(\b[A-Z])#(\1)#g'
(T)he (G)eek (S)tuff

[root@kube-node1 ~]# sed -r 's#^([^,]*),([^,]*),([^,]*)#\1,\3#' test.txt           #注意这里的[^,]用法,逗号可换成其他任何分隔符,对于sed分隔取不同的段非常拥有
101,Test1
102,Test2
103,Test3
104,Test4

 

 

字符类

若要查找数字(\d),字母或数字(\w),空白(\s)是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?

很简单,你只需要在方括号里列出它们就行了:

第一种形式:

像[aeiou]就匹配任何一个英文元音字母,[.?!]匹配标点符号(.或?或!) 。

第二种形式:

指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_] 也完全等同于\w(如果只考虑英文的话)

更复杂的表达式: \(?0\d{2}[ ) - ]?\d{8}

 

 

匹配几种格式的电话号码,像(010)88886666 ,或022-22334455,或02912345678等

\(?\d{3}\)?-?\d{8}或\(?0\d{2}\)?-?\d{8}

 

 

分枝条件

刚才那个表达式也能匹配010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分枝条件

正则表达式里的分枝条件指的是有几种规则,只要满足其中任意一种规则成功匹配,具体方法是用 | 把不同的规则分隔开

0\d{2}-\d{8}|0\d{3}-\d{7} 这个表达式能匹配两种以连字号分隔的电话号码:

一种是三位区号,8位本地号,如010-12345678

一种是4位区号,7位本地号,如0376-2233445

\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码:

其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号空格间隔,也可以没有间隔。

你可以试试用分枝条件把这个表达式扩展成也支持4位区号的:

\(\d{4}\)[- ]?\d{8} | \d{4}[- ]?\d{8}

\d{5}-\d{4} | \d{5}这个表达式用于匹配美国的邮政编码:

美国邮编的规则是5位数字,或者用连字号间隔的9位数字

之所以要给出这个例子是因为它能说明一个问题:

使用分枝条件时,要注意各个条件的顺序

 

如果你把它改成\d{5} | \d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。

原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了(也就是说 | 的优先级高于连接,而小括号的优先级又高于 |

 

分组

重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数

实例:

(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。

要理解这个表达式,请按下列顺序分析它:

\d{1,3}匹配1到3位的数字,(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3} )。

不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。

正确的IP地址匹配:

((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

255.255.255.255

分组——向后引用

使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。

默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推

后向引用用于重复搜索前面某个分组匹配的文本

例如,\1代表分组1匹配的文本

\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go,或者kitty kitty 。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。

也可以自己指定子表达式的组名,要指定一个子表达式的组名,请使用这样的语法:

(?<Word>\w+)

把尖括号换成'也行:(?'Word'\w+))

这样就\w+的组名指定为Word

反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s+\k<Word>\b。

 

分组——零宽断言和负向零宽断言

一、零宽断言

接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b , ^ , $ 那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言

1(?=exp)

(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能否匹配表达式exp

比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。

示例:

[root@scm ~]$ ifconfig ens32
ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.4  netmask 255.255.255.0  broadcast 10.0.0.255
        inet6 fe80::33f1:7a50:cf8e:e05e  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:98:8c:d1  txqueuelen 1000  (Ethernet)
        RX packets 33642  bytes 30895875 (29.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12197  bytes 3200253 (3.0 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@scm ~]$ ifconfig ens32|grep -oP '\S+(?=  netmask)'      #注意netmask前面有2个空格,   \S+ 是要匹配的字符串
10.0.0.4

 

 

 

2、(?<=exp)

(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp

比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book 时,它匹配ading

假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对1234567890进行查找时结果是234567890。

下面这个例子同时使用了这两种断言:

(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。

使用示例: 

取IP地址:

[root@scm ~]$ ifconfig ens32
ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.4  netmask 255.255.255.0  broadcast 10.0.0.255
        inet6 fe80::33f1:7a50:cf8e:e05e  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:98:8c:d1  txqueuelen 1000  (Ethernet)
        RX packets 33331  bytes 30862449 (29.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12017  bytes 3171421 (3.0 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@scm ~]$ ifconfig ens32 | grep -oP '(?<=inet )\S+'    #注意不能使用\w+匹配, 因为\w只能匹配到字母/数字/下划线/汉字(无法匹配到IP地址中的句点.)
10.0.0.4

 

 

 

 

二、负向零宽断言

前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?

例如,如果我们想查找这样的一个单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样

\b\w*q[^u]\w*\b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w*\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting。

负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符

现在,我们可以这样来解决这个问题: \b\w*q(?!u)\w*\b。

1、(?!exp)

零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp

例如:

\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;

\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。

[root@scm ~]$ cat test
test file china
test hello china
world test china
[root@scm ~]$ cat test | grep -P '^test(?! hello)'        #注意test和hello之间有个空格, 因此这里的hello前需要有个空格
test file china

 

同理,我们可以用(?<!exp),

零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp

(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。

一个更复杂的例子:

(?<=<(\w+)>).*(?=<\/\1>)

匹配不包含属性的简单HTML标签内里的内容。

(?<=<(\w+)>)指定了这样的前缀:前缀必须是<(\w+)>

被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/(转义),它用到了前面提过的字符转义;

\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。

整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身,前缀和后缀只是位置)。

 示例:

[root@scm ~]$ cat test
test file china
test hello china
world test china
[root@scm ~]$ cat test | grep -P '(?<!hello )china$'      #hello和china之间有个空格,因此这里hello后有个空格
test file china
world test china

 

注释

小括号的另一种用途是通过语法(?#comment)来包含注释

例如:

2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)。

要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉

(?<=              #断言要匹配的文本的前缀

<(\w+)>         #查找尖括号括起来的字母或数字(即HTML/XML标签)

)                    #前缀结束

.*                  #匹配任意文本

(?=                #断言要匹配的文本的后缀

<\/\1>            #查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签

)             #后缀结束

 

 小结: 

分类 代码/语法 说明
捕获 (exp) 匹配exp, 并捕获文本到自动命名的组中, 组的名字为\1  \2  \3 ...等, 引用时直接使用\1   \2   \3...

(?(name)exp)

(?'name'exp)

匹配exp, 并捕获文本到命名的组中(name为自定义的组名)

引用时, 使用  \k<name>    格式引用

(?:exp) 匹配exp, 但不会捕获文本, 也不会分配组名称
零宽断言 (?=exp) 匹配exp前面的位置, 因此(?=exp)必须要书写在要匹配的文本的后面, 示例:  ifconfig ens32|grep -oP '\S+(?=  netmask)'    #\S+是要获取的文本字段, 文本字段的后面必须是(?=  netmask)
(?<=exp) 匹配exp后面的位置, 因此(?<=exp)必须要书写在要匹配的文本的前面, 示例: ifconfig ens32 | grep -oP '(?<=inet )\S+'  #\S+是要获取的文本字段, 文本字段的前面必须是(?<=inet )
(?!exp) 匹配后面不是exp的位置, 因此(?!exp)必须写在要匹配的文本的后面, 示例: cat test | grep -P '^test(?! hello)'       #test开头的行, test后不是" hello"的行都会被匹配出来
(?<!exp) 匹配前面不是exp的位置, 因此(?<!exp)必须写在要匹配的文本的前面, 示例: cat test | grep -P '(?<!hello )china$'      #以china结尾的行, china前不是"hello "的行都会被匹配出来
注释 (?#comment) 此类型的分组不会对正则有任何影响, 仅仅是注释信息

 

捕获的第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面,也不会拥有组号

注意:零宽断言匹配的是位置,而不是字符

 

贪婪匹配与懒惰匹配

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符(即贪婪匹配)

以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab,这被称为贪婪匹配

 

有时,我们更需要惰匹配,也就是匹配尽可能少的字符

前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?,这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复

现在看看懒惰版的例子:aabab

a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。

 

懒惰限定符:

*?                 重复任意次,但尽可能少重复

+?                 重复1次或更多次,但尽可能少重复

??                 重复0次或1次,但尽可能少重复

{n,m}?          重复n到m次,但尽可能少重复

{n,}?             重复n次以上,但尽可能少重复

综上:懒惰匹配是在贪婪匹配的基础上加一个?(元字符?表示0个或1个匹配)