正则表达式(入门)

内容节选自 “精通表达式”一书。

正则表达式:由元字符和普通文本字符组成。

元字符:

行的起始与结束


脱字符^ 代表一行的开始,美元符号$ 代表一行的结束
最好养成按照字符来理解正则表达式的习惯
如^cat 不要理解成匹配以cat开头的行,而是理解成是以c作为一行的第一个字符,紧接着一个a,紧接着一个t的文本。

字符组


匹配若干字符之一   []
对于单词grey,如果不确定它是否写作gray,这时就需要使用字符组。
gr[ea]y的意思是先找g,跟着是一个r,然后是一个e或者a,最后是一个y,如果不确定一个单词的写法,可以使用这种方式来匹配单词。
字符组的内容是在同一个位置能够匹配的若干个字符,所以它的意思是或。
这里会有一个单词嵌套的问题,[Ss]mith,依然能匹配到内嵌在其他单词里的smith,如blacksmith
字符组中可以列举任意多个字符。例如<H[123456]> 匹配<H1>,<H2>,<H3>等。

可以使用连字符'-'表示一个范围,如<H[1-6]>与<H[123456]>是一样的。
[0-9]与[a-z]常用来匹配数字和小写字母
我们还可以随心所欲把字符范围与普通文本结合起来:
[0-9A-Z_!.?]能匹配一个数字,大写字母,下划线,惊叹号,点号或者是问号。
注意:只有在字符组内部,连字符才是元字符,否则它就只能匹配普通的连字符号,同样,问号和点号在字符组内部被当成普通字符处理,否则会被当成元字符处理。

案例分析:
^cat$:行开头,然后是字母c a t,最后是行末尾。这一行只包含cat
^$ : 行开头,然后是行末尾。 匹配空行,包括空白字符
^ : 每一行都有开头,匹配每一行。

排除性字符组 [^...]


[^1-6] :匹配除了1到6以外的字符。^必须在字符组内部的开头才表示排除。
注意:一个字符组,即使是排除型字符组,也需要匹配一个字符。例如,使用egrep 工具 用q[^u] 无法匹配Irap, lrap中p作为一个末尾字符后面是有换行符的,但是egrep 把换行符替换掉了,所以匹配不到。但如果没有把换行符替换掉,是可以匹配到的。

因此,'[^x]' 的意思并不是 只有当这个位置不是x时才能匹配,而是说   匹配一个不等于x的字符。其中的差别很细微,但很重要,例如前面的概念可以匹配一个空行,而[^x] 则不行

用点号匹配任意字符

注意:点号 在[]内是一个普通字符。


多选结构


匹配任意子表达式


|  元字符表示"或",使用它能够把不同子表达式组合成总的表达式。
"Bob|Robert" 能够同时匹配其中任意一个正则表达式。在这样的组合中,子表达式成为多选分支。
'gr[ea]y' 也可写成'grey|gray',或者'gr(e|a)y',最后一种用括号来划定多选结构的范围。
注意 gr(e|a)y 与 gr[e|a]y 的区别。
多选结构与字符组的区别:一个字符组只能匹配目标文本中的单个字符,而每个多选结构自身都可能是完整的正则表达式,可以匹配任意长度的文本。

在使用多选结构表示中使用脱字符与美元符也要小心。
'^From|Subject|Date:' 与'^(From|Subject|Date):'的区别
前者由三个分支组成,匹配'^From'或'Subject'或'Date:',实用性不大。
后者则是在每一个多选分支前都有脱字符与:。

忽略大小写


正则表达式本身是区分大小写的。例如我们可以使用'[Ff][Rr][Oo][Mm]'来代替'From',匹配任何形式的from,
但是这样写比较麻烦,有些工具提供一个忽略大小写的匹配。例如egrep -i 正则。

单词分界符


顾名思义就是 匹配单词开头和结束的位置。
'\<' 和 '\>' 匹配单词分界的位置,分别匹配单词的开头和结束位置
< 和 > 本身不是元字符,只有与斜线结合起来的时候,整个序列才具有特殊意义。
注意 在使用egrep工具时,不是所有版本都支持单词分界符,
如'\<cat\>'意思是匹配单词的开头位置,然后是c a t这三个字母,然后是单词的结束位置
也可以用'\<cat' 和 'cat\>'来匹配以cat开头和结束的单词。

可选项元素


匹配color 与 colour,可以用'colou?r'来匹配。
元字符?问号代表可选项,它只作用于之前紧邻的元素,代表该元素可出现也可不出现。
例如'(July|Jul)' 可以写成 'July?'
   '(4th|4)' 可以写成 '4(th)?',现在?作用的元素就是整个括号了

其他量词:重复出现


+ 加号:表示之前紧邻的元素出现一次或多次
* 星号:表示之前紧邻的元素出现任意多次,或者不出现。
?问号:表示之前紧邻的元素出现0次或一次。
它们限定了所作用元素的匹配次数,所以它们统称为量词。
?与* 是永远不会匹配失败的,区别只在于它们的匹配结果
+ 在无法进行一次匹配时,会报告匹配失败。

规定重现次数的范围:区间


{3,12}允许重现次数在3到12之间,例如'[a-zA-z]{1,5}'可以匹配美国的股票代码(1到5个字母)

括号与反向引用


引例:匹配重复单词(两个单词连着出现两次) the the
使用'\<the the\>' 就可解决这个问题。
但是 不可能穷举出所有重复的单词,所以如果我们先匹配任意一个单词,在检查后面的单词是否与它一样。
这时就使用()括号的反向引用功能,它允许我们匹配与表达时先前部分匹配的同样的文本。
所以 改成了'\<([A-Za-z]+)空格+\1\>'
()括号能够'记忆'其中子表达式匹配的文本,使用元字符序列'\1'来调用它们
这里'\1'代表的文本就是(A-Za-z)+, 因为两个单词之间可能有空格,所以后面跟着空格+。

上面表达式还有局限性:如果重复单词的第一个单词在某行末尾,第二个单词在下一行开头,这个表达式就无法找到。

注意这里可能会有一个问题,一句话中that that,这两个连着的that 在句子中可能做不同的成分,就不能算重复的单词
但是这不是正则表达式的问题,真正的判断还需要靠人。

注意:这里的\1 匹配的不是括号里边的格式,而是括号里边的内容。

嵌套的括号:


括号具有记忆功能,需要注意嵌套括号的记忆顺序。
例子:([0-9]([A-Z]))([a-z])\1\2\3
此时:\1 代表([0-9]([A-Z])),
     \2代表([A-Z]),
     \3代表[a-z]
特别注意:反向引用\1 引用的他所记忆的内容,而非格式,
例如:对于2Aa,此时\1 引用的2A,而非与2A相同的格式:3B,4D.
所以对于上面的正则符合要求的文本是:2Aa2AAa

如果要改变嵌套括号,对\1\2的影响,可以使用(?:)
例如:([0-9](?:[A-Z]))([a-z]),此时的\2 代表的就是(a-z)

神奇的转义


转义符:\ 反斜线,把元字符变成普通字符,不过在字符组内部没有作用
前面提及的,某些版本的程序会把'\<','\>','\1'本身就当作一个元字符。

入门示例扩展


匹配数字 '^[0-9]+$'
允许输入负数和小数部分
负数:用'[-+]?' 来处理
小数:用'(\.[0-9]*)?' 来处理
综合起来 得到'^[-+]?[0-9]+(\.[0-9*])?$'
不过它现在 不能匹配以小数点开头的数。

在匹配其中的空白字符时,例如制表符\t,注意该制表符不能匹配空格,
所以需要使用字符组来匹配两者 '[ \t]*'
注意'[ \t]*' 与' *|\t*'的区别:
[ \t]* 可以匹配空格和\t的组合体。但是后者则不能。

在匹配空格时,许多流派的正则提供一种方便方法:\s.
\s能表示所有表示空白字符的字符组。
所以在匹配带小数的温度时,正则表达式变成了
^([-+]?[0-9]+(.[0-9]*)?)\s*([CF])$; 允许数值与单位符号[CF]间有任意空格。

简记法:
\t 制表符
\n 换行符
\r 回车符
\s 任何"空白"字符
\S 除\s之外的任何字符
\w '[a-zA-Z0-9]'(在\w+中很有用,可以用来匹配一个单词)
\W 除\w之外的任何字符,也就是'[^a-zA-Z0-9]'
\d [0-9],即数字
\D 除\d之外的任何字符,即[^0-9]

正则表达式-替换

使用perl 语言 利用正则表达式进行文本匹配,文本替换
文本匹配:m
例如:if($input =~ m/^([-+]?[0-9]+(.[0-9]*)?)\s*([CF])$)。
文本替换:s ; $input =~ s/regex/replacement
如果正则表达式能匹配$input中的某段文本,将这段匹配的文本替换为replacement.

需求:对于浮点型数据,通常保留小数点后两位数字,如果第三位不为零,也需要保留,去掉其他数字。
表达式:s/(\.\d\d[1-9]?)\d*/$1/
说明:最开始\.匹配小数点,接下来\d\d匹配开头两位数字。[1-9]?匹配可能跟在后面的非零数字。
把他们括起来保存到$1中,将$1 放入replace字符串中。

用环视功能为数值添加括号


298444215 变成 298,444,215
思路:逗号应该加在 左边有数字,右边数字的个数正好是3的倍数的位置。
环视结构 不匹配任何字符,只匹配文本中的特定位置。
环视分为:顺序环视(?=...),逆序环视(?<=...)
顺序环视:在?=后面表达式的前面。
逆序环视:在?<=后面表达式的后面。


对于 by Jeffrey Fried1. 文本,顺序环视(?=Jeffrey)匹配标记的位置,把顺序环视与真正匹配字符的部分结合起来,
我们能得到更精确的结果。
例如(?=Jeffrey)Jeff, 只能匹配Jeffrey中的Jeff,而不会匹配Jefferson中的Jeff

环视功能的例子


把所有格 Jeffs 替换为"Jeff's".
最简单直接:s/\bJeffs\b/Jeff's/g. \b为单词分界符锚点。
使用环视结构: s/\bJeff(?=s\b)/Jeff'/g
说明:Jeff 匹配之后,接下来尝试的是顺序环视。只有当's\b'在此位置能够匹配时(也就是Jeff之后紧跟一个's'和一个单词分界符),
整个分界符才能匹配成功,但是因为s\b只是顺序环视子表达式的一部分,所以它匹配的s不属于最终的匹配文本,他只是选择一个位置,
所以replacement中可不用包含s

如果把开头'Jeff'搬到逆序中,结果是'(?<=\bJeff)(?=s\b)',它的意思是找到这样一个位置,它紧接在'Jeff'之后,在's'之前。
此时变成了 s/(?<=\bJeff)(?=s\b)/'/g,它实际上没有匹配任何字符,只是匹配了希望插入撇号的位置。
注意:如果把两部分颠倒下位置 s/(?=s\b)(?<=\bJeff)/'/g,结果是完全一样的,它只是颠倒了两个环视结构,因为它没有占用任何字符,所以变换顺序并没有影响。

回到为数字添加逗号。


思路:左边有数字,右边数字的个数正好是3的倍数。
环视的功能就是找位置。
左边有数字,就是在一位数字的后面,用逆环视(?<=\d)
右边数字的个数正好是3的倍数,就是后面有三个数字,在三个数字的前面用顺视,(?=(\d\d\d)+$),$符号保证这些数字后面不存在其他字符。它会匹配',123,456,789'中,标记的位置
注意:2后面数字个数是7个,不是3的倍数,所以2后面没有逗号。原因是使用的是(\d\d\d)+ ,一个或多个,所以后面数字个数必须是3的倍数。
然后在使用(?<=\d) 来限定不在第一个数字之前加入逗号。
最终表达式为 s/(?<=\d)(?=(\d\d\d)+$)/,/g;

单词分界符和否定逆序环视。


对于上面的表达式,是不能匹配 123456789 adsd.. 文本的。因为有$ 是以3的倍数位 数字结尾的。
如果去掉$ 结果就会变成 1,2,3,4,5,6,789. (数字之外的字符也被记录在数字的个数中了)
这时可以采用单词分界符 \b 代替$.单词分界符:一侧是单词(例如数字),一侧不是(例如行的末尾,或者数字后面的空格)(表否定)。

否定环视:子表达式无法匹配。
否定顺序环视:(?!) ,子表达式不能匹配右侧文本。(位置在子表达式之前)
否定逆序环视:(?<!) , 子表达式不能匹配左侧文本.(位置在子表达式之后)

在逗号插入问题中,就可以使用(?!\d) 来代替 \b 或者$,得到
s/(?<=\d)(?=(\d\d\d)+(?!\d))/g

注意:否定顺序环视(?!\d) 不能代替 非数字字符'\D',\D表示不是数字的某个字符,某个字符是必须的,但是环视只匹配的位置,不包含字符。

我们还可以不通过逆序环视添加括号。
s/(\d)(?=(\d\d\d)+(?!\d))/$1,/g;肯定逆序环视变成了捕获型括号。
如果连顺序环视也不使用了呢?
s/(\d)((\d\d\d)+\b)/$1,$2/g;
结果并不能进行匹配。这就需要说明一下,使用顺序环视的意义。
因为(\d\d\d)+ 匹配的数字属于最终匹配文本,所以不能作为未匹配的部分,供/g 下一次迭代使用。在一次迭代完成后,下一次的迭代会从上一次的匹配的终点开始尝试。
我们希望在插入逗号以后,还能继续检查这个数值,已决定是否需要在插入逗号。但是这个例子中,重新开始的起点就是整个数字的末尾。
使用顺序环视的意义在于,检查某个位置,但是检查时匹配的字符不算在最终匹配的字符串内。

用  不用顺序环视的表达式可以使用 下面 这种方式进行实现要求。
while( $text =~ s/(\d)((\d\d\d)+\b)/$1,$2/g ){ 重复这个循环,直至匹配失败}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值