正则表达式必知必会—读书笔记(三)

正则表达式可以应用各种语言在各个平台上完成复杂的文本匹配工作,学习《正则表达式必知必会》记录笔记,内容主要包括文本匹配、回溯引用、条件性求值和前后查找,更详细信息可以阅读原书籍。😁

一、正则表达式简介

正则表达式:regular expression
基本场景:查找特定的信息(搜索)
查找并编辑特定的信息(替换)

举例来说“等于”比较本质上也是一种搜索操作,比如用户给出的电子邮件地址是否匹配正则表达式,这种搜索会对用户所提供的整个字符串进行搜索以寻找一个匹配。

替换操作举例可以把URL地址字符串替换为可点击URL地址的场景,需要先把URL地址字符串找出,比如搜索(http://或https://开头,以句号、逗号或以空白字符结尾的字符串),替换为HTML语言的“<AHREF=…>…元素,

http://www.forta.com/
<AHREF=“http://www.forta.com”>http://www.forts.com/</A>

二、匹配字符

2.1 匹配纯文本

正则表达式可以包含纯文本,也可以只包含纯文本。
正则表达式区分字母大小写,在不同的语言环境中也支持不区分字母大小写的匹配操作。

JavaScript可以用i标志强制执行一次不区分字母大小写的操作。
shell中sed指令也可以使用i标志执行不区分字母大小写的操作。
2.2 匹配任意字符

在正则表达式里,特殊字符(或字符集合)用来给出要搜索的东西,“.”可以匹配任何一个单个的字符、字母、数字甚至是字符“.”本身

文本:sales1.xls  order.xls sales2.xls na1.xls
正则表达式:.a.       
匹配结果:sales1.xls sales2.xls na1.xls

正则表达式: .a..
匹配结果:sales1.xls sales2.xls na1.xls

新增加的“.”将匹配任何一个多出来的字符(whatever)。

2.3 匹配特殊字符

例如如果“.”是需要的,需要在正则表达式表示出需要的是“.”字符本身,而不是它的特殊含义。必须在前面加上“\”(反斜杠)进行转义(metacharacter),“\”永远出现在一个有特殊含义的字符序列的开头,序列可以由一个或多个字符构成,接着上面例子

正则表达式:.a.\.
匹配结果:na1.xls
2.4 匹配一组字符

“.”可以匹配任意单个字符,字符集合只能匹配特定的字符和字符区间。在正则表达式里可以使用元字符[ ]定义字符集合,元字符之间的所有字符都是该集合的组成部分,字符集合的匹配结果是能够与该集合里的任意一个成员相匹配的文本。

这种操作最适合全局区分大小写,但在某个局部不需要区分字母大小写的搜索操作。

文本:na1.xls ca1.xls sa1.xls
正则表达式:[ns]a.\.
匹配结果:na1.xls sa1.xls
2.5 合理利用字符集合区间
文本:na1.xls ca1.xls sa1.xls sam.xls
正则表达式:[ns]a[123456789]\.           [ns]a[1-9]\.
匹配结果:na1.xls sa1.xls

一方面增加字符集合区间的使用增加匹配精度,另一方面引入特殊的元字符“-”(连字符),用来简化字符区间(0-9,A-Z,a-z等)的定义。

A-z,匹配从ASCII字符A到ASCII字符z的所有字母。这个模式一般不常用,因为它还包含着[和^等在ASCII字符表里排列在Z和a之间的字符。字符区间的首、尾字符可以是ASCII字符表里的任意字符。但在实际工作中,最常用的字符区间还是数字字符区间和字母字符区间。

注意点:1.字符区间的尾字符要大于等于首字符,避免正则表达式失效。2.“-”是特殊元字符,作为元字符只能用在[和]之间。在字符集合以外地方,“-”只是一个普通字符,甚至不需要用转义字符。

2.6 取非匹配

用元字符“^”来表明对某一个字符集合进行取非匹配,与逻辑非运算相似,只是这里的操作数是字符集合。作用于给定字符集合里的所有字符或字,同样接上例,

正则表达式:[ns]a[^0-9]\.
匹配结果:sam.xls

三、使用元字符

元字符是一些在正则表达式里有着特殊含义的字符,大致可以分为两种。一种是用来匹配文本,比如“.”;另一种是正则表达式的语法所要求的,比如“[”“]”。
表1 空白元字符
在这里插入图片描述
\r\n匹配一个“回车+换行”组合,有许多操作系统(比如Windows)都把这个组合用作文本行的结束标签。使用正则表达式\r\n\r\n进行的搜索将匹配两个连续的行尾标签,而那正是两条记录之间的空白行。 \r\n是Windows所使用的文本行结束标签。Unix和Linux系统只使用一个换行符来结束一个文本行;换句话说,在Unix/Linux系统上匹配空白行只使用\n\n即可,不需要加上\r。同时适用于Windows和Unix/Linux系统的正则表达式应该包含一个可选的\r和一个必须被匹配的\n。

3.1 匹配特定的字符类别

一些常用的字符集合,可以用特殊元字符来代替,这些元字符匹配的是某一类别的字符,称之为字符类。类元字符并不是必不可少,可以通过逐一列举或是定义字符区间来匹配某一类字符。

元字符说明
\d任何一个数字字符(等价于[0-9])
\D任何一个非数字字符(等价于[^0-9])
\w任何一个字母数字字符(大小写均可)或下划线字符(等价于[a-zA-Z0-9_])
\W任何一个非字母数字或下划线字符(等价于[^a-zA-Z0-9_])
\s任何一个空白字符(等价于[\f\n\r\t\v])
\S任何一个非空白字符(等价于[^\f\n\r\t\v])
3.2 使用POSIX字符类

POSIX字符是许多正则表达式实现都支持的一种简写形式,举例从一段HTML代码中查找出RGB值,

文本:<BODY BGCOLOR=“#336633” TEXT=“#FFFFFF” MARGINWIDTH=“0” MARGINHEIGHT=“0” TOPMARGIN=“0” LEFTMARGIN=“0”>
正则表达式:#[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]

#[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]
匹配结果:“#……”内容被选出

在这里插入图片描述

四、重复匹配

4.1 有多少个匹配

举例思考如何匹配一个电子邮件地址(text@text.text),三个text位置的字符数量不固定。

  • 匹配同一个字符(或字符集合)的多次重复,加上“+”字符后缀。
a+ 匹配一个或多个连续出现的a; [0-9]+匹配一个或多个连续出现的数字
  • 匹配同一个字符(或字符集合)的零次或多次重复,加上“*”字符后缀。
  • 匹配同一个字符(或字符集合)的零次或一次出现,加上“?”字符后缀。

“+ * ?”之前如果没有字符集合标志,只作用于前面单个字符。

上述三种方法可以定义匹配字符,但无法将匹配的字符个数精确为具体数字,正则表达式提供了一个用来设定重复字数的语法,重复次数要用“{”和“}”字符来给出。对应RGB示例,

[:xdigit:]匹配一个十六进制字符,出现6次写6次,可使用:
正则表达式:#[:xdigit:]{6}   或  #[0-9A-Fa-f]{6}

采用{2,4}可设置匹配次数区间,最少重复两次最多重复四次。

最后一种用法是给出最小匹配次数,{3, }

文本:$496.37  $32.73 $256.34 匹配大于100美元结果
正则表达式:\$\d{3,}\.\d{2}
匹配结果:$496.37 $256.34
4.2 防止过度匹配

很多匹配语法在重复次数没有上限值,有时会导致过度匹配的现象。如下示例:

文本:living in <B>AK</B> and <B>HI</B>.
正则表达式:<[Bb]>.*<\/[Bb]>
匹配结果:<B>AK</B> and <B>HI</B>当作一个整体被找出

*和+都是“贪婪型”元字符,进行匹配时的行为模式是多多益善,而不是适可而止,他们会尽可能地从一段文字的开头,一直匹配到这段文本的末尾;而不是从这段文本的开头,匹配到第一个匹配时为止。此时可使用“懒惰型”版本匹配尽可能少的字符。

贪婪型元字符懒惰型元字符
**?
++?
{n,}{n,}?
正则表达式:<[Bb]>.*?<\/[Bb]>
匹配结果:<B>AK</B>     和 <B>HI<B>两个结果

五、位置匹配

有时需要且只需要对某段文本的特定位置进行匹配,位置匹配用来解决在什么地方进行字符串匹配操作,需要在正则表达式里用一些特殊的元字符来表明我们想让匹配操作在什么位置发生。

5.1 单词边界

第一种边界使用\b指定单词边界,匹配一个单词的开始或结尾。

文本:the cat scattered his food
正则表达式:\bcat\b
匹配结果:cat
正则表达式:\Bcat\B
匹配结果:scattered

\b匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是与\w相匹配的字符)和一个不能用来构成单词的字符(也就是与\W相匹配的字符)之间。\b只匹配一个位置,不匹配任何字符。表明不匹配一个单词边界使用\B。同一个元字符的大写形式与他的小写形式在功能上往往刚好相反。

5.2 字符串边界

在分行匹配模式下,^不仅匹配正常的字符串开头,还将匹配行分隔符(换行符)后面的开始位置(这个位置是不可见的);类似地,$不仅匹配正常的字符串结尾,还将匹配行分隔符(换行符)后面的结束位置。

六、使用子表达式

运用子表达式(subexpression)的概念对表达式进行分组和归类。把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用,子表达式必须用“(”和“)”括起来。

文本:Windows &nbsp; &nbsp; 2000,and
正则表达式:(&nbsp;){2,}
匹配结果:&nbsp; &nbsp;

子表达式允许多重嵌套,见IP地址示例,

文本:pinging hog.forta.com [12.159.46.200]
正则表达式:(\d{1,3}\.){3}\d{1,3}
匹配文本:12.159.46.200
此种方式\d会匹配非法IP地址,例如345、700等大于255的数字,因此需满足以下要求:
    任何一个1位或2位数字。
    任何一个以1开头的3位数字。
	任何一个以2开头、第2位数字在0~4之间的3位数字。
	任何一个以25开头、第3位数字在0~5之间的3位数字。
正则表达式:(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

七、回溯引用:前后一致匹配

回溯引用(backreference)
有时会要求对于表达式的后一部分(匹配结束标签)对前一部分产生关联,需要用到回溯引用。

文本:<H1>Welcome to china</H1>
	 <H2>Hello World</H3>
正则表达式:<[Hh][1-6]>.*?<\/[Hh][1-6]>
匹配结果:文本两行均被选中,真实结果希望前后<Hx>一致

回溯引用指的是表达式的后半部分引用在前半部分中定义的子表达式,\1代表第一个子表达式,\2代表第二个子表达式,回溯引用只能用来引用正则表达式里的子表达式(用(和)括起来的正则表达式片段),接上例,

正则表达式:<[Hh]([1-6])>.*?<\/[Hh]\1>
匹配结果:<H1>Welcome to china</H1>

正则表达式可以用于复杂的替换,尤其是使用回溯引用的场合,

文本:hello ben@forta.com is my email
正则表达式:\w+[\w\.]*@[\w\.]+\.\w+
匹配结果:ben@forta.com

第一步可以将正则表达式整体()起来作为子表达式用来回溯,
正则表达式:(\w+[\w\.]*@[\w\.]+\.\w+)
第二步进行替换链接地址格式<A HREF=“maito:user@address.com”>user@address.com</A>
正则表达式:<A HREF=“maito:$1”>$1</A>

替换操作需要用到两个正则表达式:一个用来给出搜索模式,另一个用来给出匹配文本的替换模式。回溯引用可以跨表达式使用,在第一个表达式里被匹配的子表达式可以用在第二个表达式里。

八、前后查找

一般正则表达式是用来匹配文本,有时也用来标记匹配文本的位置,前后查找(lookaround)对某一位置的前、后内容进行查找。向前查找一般都支持,向后查找并非所有语言都支持。

文本:<HEAD>
     <TITLE>Ben Forta's home</TITLE>
     </HEAD>
正则表达式:<[tT][iI][tT][lL][eE]>.*</[tT][iI][tT][lL][eE]>
匹配结果:<TITLE>Ben Forta's home</TITLE>

不足:只有中间的内容才是需要的,匹配的结果还包括了对位置的定位。(<TITLE>),需要的操作是包含的匹配本身并不返回,而是用于确定正确的匹配位置,并不是匹配结果的一部分。
  • 向前查找指定必须匹配但不在结果返回的表达式,实际是子表达式。从语法上看向前查找就是一个以“?=”开头的子表达式,需要匹配的文本跟在=的后面。
文本:https://www.baidu.com
     http://www.google.cn
     ftp://ftp.forta.com
正则表达式:.+(?=:)
匹配结果:https http ftp

前一个用来匹配:的模式是(? =:),后一个用来匹配:的模式是(:)。这两个模式所匹配的东西是一样的,都是紧跟在协议名后面的那个:,它们之间的区别只是被匹配到的:字符有没有出现在最终的匹配结果里而已。

操作符说明,向前向后查找,负查找语法类似
(?=)正向前查找
(?!)负向前查找
(?<=)正向后查找
(?<!)负向后查找

理解?=和?!,可以理解前瞻,后顾,负前瞻,负后顾四个概念:

  • 前瞻:
    exp1(?=exp2) 查找exp2前面的exp1
  • 后顾:
    (?<=exp2)exp1 查找exp2后面的exp1
  • 负前瞻:
    exp1(?!exp2) 查找后面不是exp2的exp1
  • 负后顾:
    (?<!exp2)exp1 查找前面不是exp2的exp1

九、嵌入条件

举例从中找出符合要求的北美电话号码,格式可以是 (123)456-7890或者123-456-7890。表达式应该满足如果匹配左括号,表达式必须匹配右括号;否则就去匹配“-”。但仍然本章节提示并非所有正则表达式都支持条件处理。

文本:123-456-7890
     (123)456-7890
     (123)-456-7890
     (123-456-7890
      1234567890
      123 456 7890
正则表达式:(\()?\d{3}(?(1)\)|-)\d{3}-\d{4}
匹配结果:(123)456-7890
          123-456-7890
表达式分析:(\()? 匹配一个可选的左括号,用括号括起来作为一个子表达式;d{3}表示数字区号;(?(1)\)|-)是一个回溯引用条件,根据条件是否匹配选择“)”或“-”。

十、通用邮箱正则表达式

通用的邮箱标准java正则表达式:

email.matches("^[a-z0-9A-Z]+[- | a-z0-9A-Z . _]+@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-z]{2,}$")
补充: “\\.”实际上被转义为两次,\\在java中被转换为一个'\'字符,然后'\.'被传给正则,\.表示对点字符进行转义,使.就表示字符'.',而不使用它在正则中的特殊意义

参考文档

【1】《正则表达式必知必会》;Ben Forta,杨涛;人民邮电出版社
【2】通用的邮箱正则表达式
【3】在正则表达式中\.和.有什么区别?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Paul安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值