阅读 454

《正则表达式必知必会》学习笔记

#《正则表达式必知必会》学习笔记

###一、匹配单个字符——"."(英文句号) 可以匹配任何一个单个的字符(可以是单个的字符,数字甚至是"."本身)。

在同一个正则表达式里,"."可以连续出现(..)将匹配任意两个字符,也可以间隔出现在不同位置。

若需要匹配"."本身,则需要在正则表达式里面用"\"对"."进行转义。

注意:若需要搜索"\"本身,则需要对"\"进行转义,就是在"\"前面再加一个转义"\"。

提示:"."可以匹配任何一个字符,这一说法并非绝对正确。再绝大多数的正则表达式中,"."智能匹配除换行符以外的任何单个字符。

###二、匹配一组字符 "[]"定义了一个字符集合,可以匹配“[]”之中的任意一个成员文本。

例如:[ns]a./.xls将匹配到“na1.xls,na2.xls,sa1.xls”等。

**[0123456789]**表示一个是数字的字符区间,可以简化为[0-9]。以下这些字符区间都是合法的:

[a-z]

[A-Z]

[0-9]

[A-z]:将匹配从ASCII字符A到字符z的所有字符。

提示:再定义一个字符区间时,一定要避免让这个区间的尾字符小于它的首字符,例如**[3-1]** 这个区间是没有意义的。

同一个字符集合里可以给出多个字符区间,如**[a-zA-Z0-9]**

####取非匹配

如果只需要把一小部分字符排除在外的话,就用元字符"^"来表明想对一个字符集合进行取非匹配。

例如:正则**p[^0-9]**匹配一个p后面不是数字的字符,如:pa,pb等,但不会匹配到p1,p2。

###三、使用元字符 元字符是一些再正则表达式里有着特殊含义的字符,所以这些字符就无法用来代表它们本身。

例如:要匹配字符"array[0]",不能直接用正则array[0],而需要用"\"来对"[]"进行转移, 所以正则如下array\[0\](在java中只需要写为array\[0])。

若要匹配到array[0]到array[9],则正则为:array\[[0-9]\]

####匹配空白字符 空白元字符表:

元字符 说明
[\b] 会退(并删除)一个字符(Backspace键)
\f 换页符
\n 换行符
\r 回车符
\t 制表符(Tab键)
\v 垂直制表符

####匹配特定的字符类别 #####匹配数字与非数字 数字元字符表

元字符 说明
\d 任何一个数字字符(等价与[0-9])
\D 任何一个非数字字符(等价与[^0-9])

#####匹配字母和数字与非字母与数字 字母与数字和非字母与数字元字符表

元字符 说明
/w 任何一个字母数字字符(大小写均可)或下划线字符(等价与[a-zA-Z0-9_])
/W 任何一个非字母数字或下划线字符(等价与[^a-zA-Z0-9_])

#####匹配空白字符与非空白字符 空白字符非空白字符元字符表

元字符 说明
\s 任何一个空白字符(等价于[\n\r\f\t\v])
\S 任何一个非空白字符(等价于[^\n\r\f\t\v])

#####匹配十六进制或八进制数值 在正则表达式里,十六进制数值要用前缀**\x来给出。比如说,\x0A对应于ASCII字符10(换行符),其效果等价于\n**。

在正则表达式里,八进制数值要用前缀**\0来给出,数值本来可以说两位或三位数字。比如说,\011对应于ASCII字符9(制表符),其效果等价于\t**。

###四、重复匹配 ####有多少个匹配 #####匹配一个或多个字符 匹配同一个字符(或字符集合)的多次重复,只要简单的给这个字符(或字符集合)加上一个**+字符作为后缀就行。+**匹配一个或多个字符(至少一个,不匹配零个字符的情况)。

比如:

a+,将匹配一个或连续多个a字符。

[0-9]+,将匹配一个或连续多个数字。

#####匹配零个或多个字符 这种匹配需要用到元字符***,用法与+完全一样。***可以匹配该字符(或字符集合)连续出现零次或多次到情况。

#####匹配零个或一个字符 这种匹配需要用到元字符**?,用法也与+类似。?**只能匹配一个字符(或字符集合)的零次或一次出现,最多不超过一次。

####匹配的重复次数 #####为重复匹配设定一个精确值 重复次数要用**{2},中间的数字为重复次数。意味着{2}**前的一个字符(或字符集合)必须在原始文本里面重复出现2次才算是一个匹配。

#####为重复匹配设定一个区间 **{}**还可以用来为重复匹配次数设定一个区间——也就是为重复匹配次数设定一个最小值和一个最大值。 如:

{2,6},表示最少重复2次,最多重复6次。

#####匹配“至少重复多少次” **{3,}**表示至少重复3次,不限制最大重复次数

####防止过度匹配

+*都是“贪婪型”元字符,在进行匹配时会尽可能的从一段文本的开头一直匹配到一段文本的结尾,而不是从这段文本的开头匹配到碰到的第一个匹配时为止。在“贪婪型”元字符后面加上一个?,就变为了“懒惰型”元字符,“懒惰型”元字符尽可能匹配到少的字符,与“贪婪型”行为刚好相反。

常用的贪婪型元字符和懒惰型版本

贪婪型元字符 懒惰型元字符
* *?
+ +?
{n,} {n,}?

例如:

一段为本为:This offer is not available to customers living in <B>AK</B> and <B>HI</B>

我们想匹配出里面的<B>标签

使用贪婪型正则:**<B>.*</B>的匹配结果为:<B>AK</B> and <B>HI</B>。因为*是贪婪型,它会尽可能多的匹配到最后一个位置。 使用懒惰型正则:<B>.*?</B>**的匹配结果为<B>AK</B>和<B>HI</B>。这才是我们真正想要的结果。

###五、位置匹配 ####单词边界 单词边界由限定符**\b指定,\b**指定一个单词的开头或结尾。

\b匹配这样一个位置,这个位置位于一个能够用来构成单词的字符(字母、数字、下划线,也就是与**\w相匹配的字符)和一个不能用来构成单词的字符(也就是与\W**相匹配的字符)之间。

注意:如果想匹配一个完整的单词,必须在单词的前后都加上**\b**限定符。例如:

/bcat/b能匹配到单词cat

/bcat能匹配到所有以cat开头的任何一个单词

cat/b能匹配到所有以cat结尾到任何一个单词

如果不想匹配一个单词边界,则使用限定符**/B**

####字符串边界 用来定义字符串边界的限定符有两个:一个是用来定义字符串开头的**^, 一个是用来定义字符串结尾的$**。

任何一个xml文档都以“<?xml version = "1.0"?>”开头,为了判断一个xml文件是否合法,那么正则为:^\s*?<?xml.*??>

在判断一个web页面是否结束时,需要判断</Html>后没有其它内容了,正则表达式为:</[Hh][Tt][Mm][Ll]>\s*$

####分行匹配模式 分行匹配模式的符号为**?m**,分行匹配模式将使得正则表达式引擎吧行分隔符当做一个字符串分隔符来处理。分行匹配模式下,**^不仅匹配正常的字符串开头,还匹配行分隔符后面的开始位置;$**不仅匹配字符串结尾,还将匹配行分隔符之前的位置。

在使用行分隔符**?m**时,必须写在整个正则表达式的最前面。例如一段javaScript代码如下:

    <SCRIPT>
    function doSpellCheck(form,field){
    	//Make sure not empty
    	if(field.value == ''){
    		return false;
    	}
    	//Init
    	vat windowName = 'spellWindow';
    	car spellCheckURL = 'spell.cfm?formname=comment&fieldname='+field.name;
    	...
    	//Done
    	return fales;
    }
    <SCRIPT>
复制代码

使用正则表达式将这段代码中的注释类容全部找出来:

正则为:(?m)^\s*//.*$

运行结果为:

//Make sure not empty

//Init

//Done

###六、使用子表达式 子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来处理。子表达式必须使用**()**括起来。例如:

正则表达式:&nbsp;{2,}将会匹配到2个以上连续的“;”。如果我们需要匹配2个以上连续的“&nbsp;”的话,则需要用到子表达式,正则为:(&nbsp;){2,}

另一个例子:

IP地址是由以英文句号分隔的四组数字,如192.168.1.1。每组数字可以为1个、2个、3个数字字符构成,那么我们可以写出正则表达式为:\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}。这个正则其实是正确的,但是**\d{1,3}.在上面出现了三次,通过子表达式来简化后,可以写成:(\d{1,3}.){3}\d{1,3}**。

####子表达式的嵌套 子表达式可以嵌套,可以多重嵌套,严格来说,是没有层级限制的。

上面匹配IP地址的正则表达式:(\d{1,3}.){3}\d{1,3}。所有正确的ip都能与之匹配,但是这还会匹配到不是合法ip的情况,如299.777.888.9。因为ip的每一组数字的值不能超过单个字节的表示范围255。

所以,一个正确的ip地址的每一组数字满足下面这些条件:

  • 任何一个1位或2位数字
  • 任何一个以1开头的3位数字
  • 任何一个以2开头、第2位数字再0~4之间的3位数字
  • 任何一个以25开头、第3位数字再0~5之间的3位数字

综合上面的所有条件,正确的匹配一个ip地址的正则为:

(((\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]))

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

匹配出下面这段文本中的重复单词:

This is a block of of text,several words here are are repeated, and and they should not be.

正则为:[ ]+(\w+)[ ]+\1

对上面正则的解释: [ ]+匹配一个或多个空格,(\w+)是一个子表达式,匹配一个或多个字母数组字符,[ ]+匹配随后的空格。这里,将\w+写为一个子表达式的目的是方便后面引用。\1就是一个回溯引用,它引用的正是前面的子表达式(\w+)。

回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式。\1代表着正则里面的第一个表达式,\2则代表第二个表达式,以此类推。

在html中,用H1~H6来表示标题(H不区分大小写),为了匹配出一段html代码中的所有标题,则可以使用正则:<[Hh]([1-6]).*?</[Hh]\1>

####回溯引用在替换操作中的应用

有一段文本如下:

Hello,fzp0804@163.com is my email address.

其中包含一个邮箱地址,通过正则**\w+[\w\.]*@[\w\.]+\.\w+**搜索出其中的邮箱地址。假设,现在需要替换为可点击链接,操作如下

搜索:(\w+[\w\.]*@[\w\.]+\.\w+)

替换:<a href="email:\$1">;$1</a>

结果:<a href="email:,fzp0804@163.com">,fzp0804@163.com</a>

这里第一个正则用于匹配,第二个正则用户替换,将第一个正则写为一个子表达式,则可以在第二个正则里面引用,通过"$1"的形式。

####大小写转换 用来进行大小写转换的元字符:

元字符 说明
\E 结束\L或\U转换
\l 把下一个字符转换为小写
\L 把\L到\E之间的字符全部转换为小写
\u 把写一个字符转换为大写
\U 把\U到\E之间的字符全部转换为大写

例,H5中的一级标题为:

<H1>Welcome to my Homepage</H1>

将一级标题的文字全部转为大写:

查找:(<[Hh]1>)(.*?)(</[Hh]1>)

替换:\$1\U\$2\E$3

###八、前后查找 ####前后查找 提取出一段html中的title内容。文本如下:

<head><title>This is a title</title>

正则为:

<[Tt][Ii][Tt][Ll][Ee]>.*?<[Tt][Ii][Tt][Ll][Ee]>

这种匹配方式会匹配到前后的title标签,如果不需要title标签,而只想要title中的内容的话,则需要用到前后查找。

####向前查找 向前查找指定了一个必须匹配但不在结果中返回的模式。从语法上看,一个向前查找的模式其实就是一个以"?="开头的子表达式,需要匹配的文本跟在"="后面

例,文本如下:

http://www.baidu.com

https://www.baidu.com

ftp://baidu.baidu.com

提取出URL中的协议名称部分

正则:.+(?=:)

结果:

http

https

ftp

若不使用向前查找元字符,正则:.+:

结果为:

http:

https:

ftp:

提示:

任何一个子表达式都可以转换为向前查找表达式,只要给它加上一个"?="前缀即可

####向后查找 向后查找的操作符为"?<="。

例,文本如下,现在只需要提取出商品的价格:

ABC01:$123

BCD02:$190

CDF03:$234

正则:\$[0-9.]+

结果:

$123

$190

$234

如果不想要""符号怎么办呢?仅仅是在之前的正则中去掉""可以吗?去掉"$"符号的正则为:[0-9.]+

结果为:

01

123

02

190

03

234

显然,不是需要的结果,需要确定应该匹配哪些文本。利用向后查找来实现,正则为:(?<=\$)[0-9.]+

结果为:

123

190

234

得到了想要的结果。

注意:

向前查找模式的长度上可变的,它们可以包含"."和"+"之类的元字符,所以它们非常灵活。

而向后查找模式智能固定长度——这是一条几乎所有的正则表达式实现都遵循的限制。

####把向前查找和向后查找结合起来 向前查找和向后查找可以组合在一起使用,如下:

文本:

<head><title>This is a title</title>

正则:(?<=<[Tt][Ii][Tt][Ll][Ee]>).*?(?=</[Tt][Ii][Tt][Ll][Ee]>)

结果:

This is a title

得到了期望结果。

####对前后查找取非 前面用到的向前向后查找都被称为“正向前查找”和“正向后查找”。前后查找还有一种叫“负前后查找”。

负向前查找:向前查找不与给定模式相匹配的文本

负向后查找:向后查找不与给定模式相匹配的文本

可以把“负”理解为“取非”,更好理解。

各种前后查找操作符

操作符 说明
(?=) 正向前查找
(?<=) 正向后查找
(?!) 负向前查找
(?<!) 负向后查找

例,文本如下:

I paid $30 for 100 apples,50 oranges, and 60 pears. I saved $5 on this order.

查找且只查找价格,正则:(?<=$)\d+

结果:

30

5

查找且只查找数量,正则:\b(?<!$)\d+\b

结果:

100

50

60

正则表达式里面还支持嵌入条件,但是在java中不支持,并且也很少用到,所以这里句不做介绍,后面附上《正则表达式必知必会》的pdf文档百度云云盘下载地址,感兴趣的同学可以自己去了解学习!

正则表达式必知必会.pdf

密码:u8fh

笔记中,如有错误,欢迎指出!!