tcl中正则表达式

1、     介绍

追根溯源,正则表达式是在1956年的时候,人类最先研究神经网络的产物,但随着时间的流逝,几乎全部编程语言都加入了对它的支持,hoho~其实这个东西也是程序员开发中比较有名的一个难点。可是不要觉得它只能用于程序开发,在Unix/Linux系统管理中它也有极为普遍的应用。git

不要认为正则表达式很可怕,用直白的话来讲,正则表达式就是利用26个英文字符与一些特殊符号的配合来进行文字内容比对的方法,绝大部分状况下,26个英文字符都表明它们自己,但在特殊符号的辅助下,这些英文字符也会有其余的含义,正则表达式比较困难的地方,也就在这种字符的2义性上面,这篇文档中对于这种具备字符2义性的地方,都会有专门的标注和说明。程序员

若是用过Dos/Windows/Linux中的通配符,就能够理解正则表达式的做用了,通配符用*号匹配任意多的任意字符,用?号匹配任意的一个字符,正则表达式有更加复杂的一套匹配系统,能够用来匹配几乎全部但愿匹配的文字内容。正则表达式

2、     文档约定

本文档中的全部实例,都是在如下环境中调试和运行的:编程

操做系统:           CentOS4.1  (Linux 2.6.9-11)网络

编程语言:           TCL8.4ssh

文本编辑器:         VIM6.3.46tcp

文档格式约定:编程语言

实例的解释性文字,使用华文楷体小四号蓝色字体显示编辑器

 

实例的解释性文字学习

 

系统或程序输出,使用浅蓝色底纹表示

系统或者程序输出

 

 

特别须要注意和标注的地方,将以笑脸符号专门表示

   ☺ 看我可爱吗?

3、     基本正则表达式

正则表达式中,26个英文字符表明它们自己,可是下面表格中的特殊字符则赋予了更多不一样的含义,必定要记住它们,由于它们是一切正则表达式的基础

特殊字符

简要说明

.

一个点,匹配任意一个字符

*

星号,匹配前面模式中的零个或者任意个

+

加号,匹配前面模式中的一个或者任意个

?

问号,匹配前面模式中的零个或者一个

()

括号,建立一个子模式

|

竖号,交替匹配

[]

中括号,用来表示一个区间

^

尖号,将一个模式挂靠在要匹配的字符串的最前面

$

美圆号,将一个模式挂靠在要匹配的字符串的最后面

别看基本正则表达式就是这么9个符号,可是想彻底理解和用好它们,仍是很困难的,为了加深理解,我来详细的说明一下,这也是我本身学习时的理解和心得,请仔细的阅读。

这些符号的做用须要多方位理解,我大概是根据符号所属的类型以及它们所起的做用这2个方向来理解它们的。

按照类型划分,上面表格中的特殊字符分为几个类型:

字符关键字:

这部分关键字包括26个英文字符(上面的表格没有列出来)。这些关键字的特色就是它们匹配自身。

数量关键字:

这部分关键字包括 . (点) * (星号) + (加号) ? (问号)这4个关键字,这中间 . (点)这个关键字稍微特殊一点,由于它有2个做用:既能够做为字符关键字表示任何字符,又能够做为数量关键字表明1个字符。

 

【任何字符】这个含义很深,由于——空字符也算任何字符,也就是说一个点能够表示有一个字符,也能够表示没有字符,这个概念是新手很容易犯错的地方。

数量关键字自己没有任何用处,它必须和【模式】这个概念一块儿共同做用,在正则表达式中,【模式】能够说是最为核心也最为普遍的内容。整体来讲,模式就是用来表示本身想匹配字符的方法,但实际上模式的概念要更为复杂和普遍,这部份内容我会在后面有更详细的描述,就如今来讲,你只要理解,数量关键字必须与模式一块儿共用就能够了。

模式关键字:

() (括号) | (竖号) [] (中括号) ^ (尖号) $ (美圆号)这5个符号都属于模式关键字,它们要么表明模式自己(括号、竖号、中括号),要么做用于模式为模式提供其余更高级的功能(尖号、美圆号)。

 

如今,咱们从另外一个角度来看这些关键字,下面的内容,详细说明这9个关键字所起的做用以及实际表达方法,这部分会有一些比较详细的说明和实例,可是在此以前,咱们必须了解一下什么是模式:

 

什么是模式?

模式就是一组用来匹配字符的关键字集合,一个最小的模式只有一个关键字,而大的模式则能够有无数个关键字:

A      这是一个模式,表明A这个字符自己

A+     这也是一个模式,表明一个或者任意多个A字符

正则表达式中,数量关键字都是做用于左边模式的,上面的例子中,A是一个没有数量关键字的模式,而A+中的+号就向左做用于前面这个A模式,若是没有A这个模式,+号自己是没有任何意义的,这里A虽然是一个字符,可是我以为把A称为模式能更清楚的理解模式的含义。

正则表达式的核心就是对模式的掌握和操做,理解了模式就等于拿到了开启大门的钥匙。

这里我介绍一个TCL语言中的命令:regsub,这个命令的做用就是利用正则表达式来获取想要的字符,它的使用方法以下:

regexp  [选项]  <正则表达式>  <匹配的原始字符串>  <保存匹配后字符串的变量>  [其余保存子模式匹配字符串的变量]

上面regexp中用[]括起来的部分是可选的,其余<>括起来的部分是必须的,若是正则表达式匹配从原始字符串中匹配到了内容,则命令返回1而且将匹配到的内容<保存匹配后字符串的变量>中。下面咱们来看1个简单的例子:

regexp  {A+}  "AABBCC"  match

puts  $match

AA

上面的puts命令用来打印match变量中的内容,A+这个模式从AABBCC这个原始字符串中匹配到了AA这2个字符,并将它置于match这个变量中,这就是一个最基本的正则表达式使用过程。

正由于模式如此重要,下面的内容就要详细说明几个模式关键字的做用了:

()    子模式匹配关键字

小括号用来将一个大模式分为几段更小的模式,这样就能够更加精细的控制匹配方式了,咱们来看一个例子:

regexp -- {(AA)(BB)(CC)} "AABBCC" match sub1 sub2 sub3

puts "The match is:$match"

puts "The sub1 is:$sub1"

puts "The sub2 is:$sub2"

puts "The sub3 is:$sub3"

The match is:AABBCC

The sub1 is:AA

The sub2 is:BB

The sub3 is:CC

上面的例子中,处于{}之间的内容是一个完整的正则表达式,在正则表达式里面咱们用()将表达式分为3个子模式,后面的match变量中保存全部已经匹配到的字符,而几个sub?变量则保存相应子模式中匹配到的字符。

 

|     交替匹配关键字

交替匹配用来匹配|符号二边的一个模式,好比下面的例子:

TOPSEC|topsec

上面的表达式表示匹配要么是所有大写的TOPSEC,要么是所有小写的topsec,不能2个都同时匹配。

 

[]    区间匹配

区间匹配用来表示匹配一系列字符串中间的一个,好比下面的例子:

regexp {[ADEFG]} "AAABBBCCC" match

puts $match

A

上面的表达式表示匹配ABCDE这5个字符中的一个,注意:只是一个

若是想匹配多个呢?可使用数量关键字辅助:

regexp {[ADEFG]+} "AAABBBCCC" match

puts $match

AAA

 

区间匹配还可使用[a-z]这样的语法来表示匹配从小写a到小写z这26个小写字母中的一个

这个关键字使用必须很是当心,由于在TCL语言中[]还有另一个含义:全部处于[]中的内容是一条TCL命令,所以在regexp中使用的时候,必须用{}将[]的其余含义取消掉,若是将{}换成"",那么上面的命令会报错。

 

^     挂靠匹配,将模式挂靠在字符串的开头

这是一个很特殊的关键字,它不像其余关键字是做用于左边的模式上,而是做用于右边的模式上,千万注意这一点!它表示从要匹配的字符串的最前面开始匹配,咱们来看一个比较的例子:

regexp  {(AAA)}  "BBBAAACCC"  match

能够匹配到,match中的值是AAA,可是咱们加上挂靠匹配字符以后呢:

regexp  {^(AAA)}  "BBBAAACCC"  match

 

没法匹配,match中的值为空,由于^符号要求必须从要匹配的字符最前面开始匹配,惋惜要匹配的字符最前面是BBB,因此没法匹配到。

^这个字符也有2义性,若是把它放在中括号里面的话,它表示【非】的意思,好比[^a-z]表示匹配不是a-z字母的其余字符,可是不在中括号里面,好比^ab表示必须最前面是ab这2个字符,这是很容易搞混的地方,必定要注意了。

 

$     挂靠匹配,将模式挂靠在字符串的结尾

这个关键字与^关键字做用相反,可是它和其余关键字同样,是做用于左边的模式上,仍是看看例子:

regexp  {(AAA)$}  "BBBCCCAAA"  match

能够匹配到,由于要匹配的字符最后面是AAA,若是要匹配的字符是BBBAAACCC这样的,就没法匹配到了。

 

数量关键字:

. (点) * (星号) + (加号) ? (问号)用来表示数量。

.     匹配任意一个字符

.(点)是一个比较特殊的字符,它虽然表示匹配任意一个字符,但实际上任意字符也包括空字符。

 

*       匹配前面模式中的零个或任意多个

零个这个概念很重要,也就是说无论有没有都会匹配,因此通常咱们都会用.*这样的方式来表示任意多个任意字符,无论有没有均可以。

+      匹配前面模式中的1个或任意多个

 

?      匹配前面模式中的0个或1个

?号还有一个术语——非贪婪模式,这也是正则表达式中很是重要的内容,所谓非贪婪模式,就是表示只要匹配到第一个就会停下来,而贪婪模式正好相反,它会尽量多的匹配,这2种模式的最终结果就是:非贪婪模式老是得到第一个匹配,贪婪模式老是得到最后一个匹配。默认状况下,正则表达式老是处于贪婪模式下的。

基本正则表达式中还有一个很重要的符号:/(反斜杠),它用来关闭上面这些特殊字符的特殊含义,好比:

/*     表示一个星号自己

/+     表示一个加号自己

//     表示一个反斜杠/(o(∩_∩)o...哈哈,本身关闭了本身)

在高级正则表达式中,反斜杠还有更多的用途。

4、     高级正则表达式

高级正则表达式是基本正则表达式的扩展,整体来讲,高级表达式扩展了如下3个方面的功能:

1.     反斜杠字符序列

我的认为反斜杠字符序列应该是高级正则表达式最为实用的扩展了,利用反斜杠加上特定字符,能够表示复杂的含义,下面的表格就是根据个人经验使用最多的反斜杠序列,我会根据使用频率从上到下的安排顺序。

反斜杠序列

简要说明

/b 匹配单词边界,即单词与旁边的空格
/B 匹配非单词边界,即单词内部

/d

表示0-9之间的数字

/D

除了0-9之间数字的其余字符,与/d做用相反

/s

空白符,包括空格、换行、回车、制表、垂直制表、换页符等

/S

非空白符,与/s做用相反

/w

数字、字母和下划线

/W

非数字、字母和下划线的其余字符

/uXXXX

16位Unicode字符编码.中文unicode编码范围[\u4e00-\u9fa5]

也能够用非双字节 [^\x00-\xff],其中包含中文

/n

换行符,Unicode码是/u000A

/r

换页符,Unicode码是/u000D

/t

制表符,Unicode码是/u0009

2.     字符类

除了反斜杠字符序列,高级正则表达式还支持字符类匹配,字符类就是利用一个单词表明复杂意思,大部分的字符类与反斜杠序列含义相同,但也有一些字符类是特有的,好比匹配16进制字符的xdigit,几乎全部状况下只要使用字符类就必须将它们放在[[: :]]符号中,下面的表格列出了全部字符类:

字符类

简要说明

[[:alnum:]]

大小写字母和数字,不包括下划线

[[:alpha:]]

大小写字母

[[:blank:]]

空格和制表符

[[:cntrl:]]

控制字符,也就是ASCII码表中1-31号的字符

[[:digit:]]

0-9之间的数字,与/d的含义相同

[[:graph:]]

全部能够显示的字符

[[:lower:]]

小写字母

[[:print:]]

alnum的另一种表示方法

[[:punct:]]

全部标点字符

[[:space:]]

空白字符,与/s的含义相同

[[:upper:]]

全部大写字母

[[:xdigit:]]

全部16进制数字,包括0-9 a-f A-F

 

3.     扩展的正则表达式语法

扩展语法中,我认为最为重要和方便的就是{}语法,它能够精确指定前面模式匹配的次数,{}语法有3种基本使用方法:

{m}       匹配前面模式的m次

{m,}      匹配前面模式最少m次,最多无限次

{m,n}     匹配前面模式最少m次,最多n次

在实际使用时还能够在{}语法后面加上 ? 号表示非贪婪匹配。

5、     实例详细说明

下面的实例都是能够单独运行的代码段,有兴趣的话能够本身将它们复制到文件中运行,观察一下它们的结果,而后修改表达式中的字段观察它们的不一样表现,这是学习正则表达式的捷径。

1.     从tcpdump中,提取IP和端口号。

set dumpoutput {

16:49:52.278091 IP 10.11.105.15.2093 > 10.11.105.102.ssh: . ack 167128 win 14944

16:49:52.292780 IP 10.11.105.15.2093 > 10.11.105.102.ssh: . ack 167332 win 16232}

 

set pattern {.*(10.11.105.15)/.+?(/d+)/s+?>+?}

set status [regexp $pattern $dumpoutput tp iptp port]

puts "ip is:$iptp"

puts "port is: $port"

ip is:10.11.105.15

port is: 2093

上面的代码中,dumpoutput变量是从tcpdump程序中截获的报文,最重要的正则表达式是pattern变量中的内容,观察一个正则表达式,应该首先观察它的子模式,从子模式中通常咱们能够看到正则表达式中最重要最核心的部分,而后再观察外围的其余字符。

上面的代码中有2个子模式,第一个子模式用来匹配IP地址,第二个子模式则使用高级正则表达式中的反斜杠字符序列,/d表示任意数值,后面的+?则用来匹配任意多个数值。

外围的代码中,大量使用了?的非贪婪特性,其中/s这个反斜杠序列表示任意空白符号。

2.     从tcpdump中,提取arp应答信息

set dumpout {17:14:24.927839 arp who-has 10.11.105.254 tell 10.11.105.102

17:14:24.927936 arp reply 10.11.105.254 is-at 00:13:72:35:a6:fd}

 

set pattern {arp reply 10.11.105.254}

set st [regexp -- $pattern $dumpout match]

puts $match

这个正则表达式很简单,就是让关键字一个一个的对应匹配,其实刚刚开始写正则表达式有一个小技巧——首先将关键字所有复制出来,而后一点一点的替换,好比将空格替换成/s+,数值替换成/d+等等。

3.     检查arp表中是否清空了指定IP的arp记录

set pcarp {

Address                  HWtype  HWaddress           Flags Mask            Iface

10.11.105.29                     (incomplete)                              eth0

10.11.105.19             ether   00:11:D8:35:13:84   C                     eth0}

 

set pattern {(10.11.105.29)+?.*?incomplete+?}

set patt "/u000A*/u000D*"

regsub -all -- $patt $pcarp {} pcarp

set st [regexp -- $pattern $pcarp match]

puts $match

10.11.105.29                     (incomplete

上面的表达式使用了?这个非贪婪匹配关键字

4.     从FW上获取系统当前时间

set fwout {+00 2007-07-24 08:25:38}

 

set pat {.*(/+[0-9]{2})/s+([0-9]{4}-[0-9]{2}-[0-9]{2})/s+([0-9]{2}:[0-9]{2}:[0-9]{2}).*}

set st [regexp $pat $fwout - t1 t2 t3]

puts "time area:$t1/ndate:$t2/ntime:$t3"

 

set pat {([0-9]{2}):([0-9]{2}):([0-9]{2})}

regexp $pat $t3 - hour minute second

puts "hour:$hour/nminute:$minute/nsecond:$second"

 

set pat {([0-9]{4})-([0-9]{2})-([0-9]{2})}

regexp $pat $t2 - year month date

puts "year:$year/nmonth:$month/ndate:$date"

这个表达式使用了高级正则表达式中的概念,在模式后面用{}括起来的数字表示匹配前面的模式多少次,利用子模式能够单独提取内容。

下面的实例除非必要就再也不解释,请仔细观察。

5.     从ifconfig 端口号中,得到IP地址。

set result [exec ifconfig eth1]

set pat {(inet addr:)([^/s]+)/s+(Bcast:.*)}

regexp $pat $result - - ip

puts "ip is :$ip"

regexp命令中的-表示不获取那个子模式中的值,由于这里使用了2个-,所以ip变量获取的就是第2个子模式的值了(第一个-获取整个表达式匹配的全部字符,第二个-获取第一个子模式中的值。

6、     后记

正则表达式使用极为灵活,特别是字符2义性的问题新手很容易出错,惟一的办法就是多使用、多练习,在错误中慢慢领会语法的含义。虽然我在写这篇文档时想尽可能加入本身的经验和理解,但实际上不少东西都是只能意会的,若是非要说清楚的话,不光语言会冗长无味,并且更容易把读者带入不知所措的境地,因此这里我尽可能将平时使用最为频繁的功能以及最容易犯错的地方指出来,其余的就要靠读者本身试验了~