正则表达式必知必会的笔记

元字符表

下面介绍的是在正则表达式中有特殊意义的字符,称为元字符

正则表达式符号表

正则表达式符号表2

转义符号

如果要查找这些字符本身,需要用\进行转义,比如想匹配点号. 就需要这样写\. 有个特殊的例子:如果想匹配\就需要这样\\

匹配单个字符

匹配字符中的一个

  • .: 匹配任意一个字符(除了换行符)
  • []: 匹配多个字符中的一个
    • 自定义字符:[akm],匹配a、k、m中的一个
    • 字符区间:[ASCII1-ASCII2],可以是ASCII中的任意字符作为起始字符(ASCII1小于ASCII2),常用的有:[a-z]、[A-Z]、[0-9]、[A-Za-z](可以有多个字符区间
    • 取反操作:[^a-z] 表示匹配不是a到z的任意字符,^的作用效果是整个区间,而不仅仅是跟在其后的字符

      -是一个元字符,表示区间,不会匹配减号,因此不需要转义

      ^是一个元字符,表示在[ ]中表示非匹配,不会匹配到^,因此不需要转义

匹配空白字符

也就是不可见的字符

符号 用途
\f 匹配一个换页符。
\n 匹配一个换行符。
\r 匹配一个回车符。
\t 匹配一个制表符。
\v 匹配一个垂直制表符。

匹配特定一类的字符

不可见字符

符号 用途
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [ ^\f\n\r\t\v]。

数字

\d 等价于[0-9]
\D 等价于[^0-9]

字符

匹配大小写字母、数字、下划线

\w 等价于[a-zA-Z0-9_]
\W 等价于[^a-zA-Z0-9_]

匹配中文

需要切换匹配模式为‘u’,表示要匹配的内容为unicode编码内容,汉字的unicode起始编码是:0x4e00—0x9fa5,因此要匹配全部为中文的正则为:$patern = "/[\x{4e00}-\x{9fa5}]/u"

或匹配“|”的一个坑

正则是从前向后进行匹配的,因此如果文本是餐饮店,而正则是餐|餐饮餐饮|餐 ,那就谁在前就先匹配到谁,因此结果分别是:餐饮

但是,如果正则是饮店|店店|饮店呢,他们的结果都是饮店,原因就是从先向后扫描时,饮店这个字会先触发,完成匹配,而不会再取匹配了。

重复匹配

符号 用途
+ 出现1至多次
* 出现0至多次
出现0或1次
{n} 精确控制出现n次
{n,m} 区间控制,出现n至m次
{n,} 至少出现n次

贪心匹配

+、*、{n,}会贪婪的进行匹配,比如下面的例子:

<b>text1</b> <b>text2</b>

<[Bb]>.*</[Bb]> 会匹配着整体字符串,而不是两个<b>text1</b> <b>text2</b>

因此需要懒惰匹配

<[Bb]>.*?</[Bb]> 会匹配到两个<b>text1</b> <b>text2</b>

贪婪版 懒惰版
* *?
+ +?
{n,} {n,}?

位置匹配

单词边界(boundary)

\b:匹配单词的开始或者结束位置,也就是单词边界:单词和符号之间的边界

单词可以是中文字符,英文字符,数字

符号可以是中文符号,英文符号,空格,制表符,换行

具体来说:\b匹配的位置是这样的位置,这个位置位于一个能够用来构成单词的字符(\w)和一个不能用来构成单词的字符(\W)之间

例子:

\bcat\b 可以匹配到 the cat catcat 中的第一个cat,因为它前面与后面都有一个单词,因此存在单词分隔符,而不会匹配到后面的catcat

这里有个一开始看不懂的例子:

# 为什么cat匹配不到,因为cat是单词,它前面与后面都有一个单词,因此存在单词分隔符
>>> re.findall(r"\Bcat\B","the   cat  cao")
[]
# -却能匹配到,因为-不是单词,所以尽管它前后都有单词,但与-之间不存在单词分割符(除了-,其他\W也是一样的)
>>> re.findall(r"\B-\B","color  -  code")
['-']

注意:java与python中有个地方不同,一个匹配失败,一个成功

String str = " 2 ";
String rex = "\b2\b";

Pattern pattern = Pattern.compile(rex);
Matcher matcher = pattern.matcher(str);

if (matcher.matches())
    System.out.println("匹配成功");
else
    System.out.println("匹配不成功"); // 这是结果
>>> s=" 2 "
>>> m=re.search(r'\b2\b',s)
>>> m
<_sre.SRE_Match object; span=(1, 2), match='2'>

字符串边界

符号 用途
^ 默认模式下:匹配整个字符串开始位置;在多行模式下:可以匹配换行符后的开始位置
$ 默认模式下:匹配整个字符串结束位置;在多行模式下:可以匹配换行符后的结束位置

子表达式(分组)

()将正则表达式括起来的就是子表达式,子表达式可以嵌套

例子:

年份

19|20\d{2}    错误,这会匹配19或者20xx
(19|20)\d{2}  正确

IP

* 任意的1位或2位数字
* 任意的以1开头的3位数字
* 任意的以2开头,0-4为第二位的3位数字
* 以25开头,第3位数字是0-5的3位数字
如下,实现了匹配上面的合法的0-255之间的数字
(\d{1,2})  | (1\d{2})  | (2[0-4]\d) | (25[0-5]) 
因此匹配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]))

子表达式嵌套

使用的是深度优先搜索,以下面的例子来解释:

  • group(0)代表整个正则表达式
  • group(1)代表第1个子表达式,也就是最外层的括号
  • group(2)代表第2个子表达式
    子表达式嵌套问题
>>> s="jizx333hhhh"
>>> m=re.search(r"((\w+?)(\d(\d(\d)))(\w{4}))",s)
>>> m.group(0)
'jizx333hhhh'
>>> m.group(1)   # 上图中的根节点
'jizx333hhhh'
>>> m.group(2)
'jizx'
>>> m.group(3)
'333'
>>> m.group(4)
'33'
>>> m.group(5)
'3'
>>> m.group(6)
'hhhh'
>>> m.group(7)
IndexError: no such group

不保存分组?:

(:X)在正则中表示所匹配的子组X不作为结果输出,称为非捕获分组,不想被捕获的时候使用,可以提高程序执行速度

正常情况(X)中的X会被作为新增的一个组序号输出,比如(A)(B),A的序号1,B的序号2
如果(?:A)(B),A将没有序号不输出,B的序号为1

import re
text="我的电话是1234567890"
pat1=r"(\d{3})(\d{3})(\d{4})"
pat2=r"(?:\d{3})(\d{3})(\d{4})"

match=re.search(pat1,s)
print(match.groups())

match=re.search(pat2,s)
print(match.groups())

# ('123', '456', '7890')
# ('456', '7890')

python中的一个坑

python中split方法,在匹配部分加上括号 ()之后所切出的结果是不同的,匹配模式加括号表示保留匹配到的分隔子串

import re
res=re.split("\|" , ' runoob|runoob|runoob.')
# [' runoob', 'runoob', 'runoob.']

res=re.split("(\|)" , ' runoob|runoob|runoob.') # 添加括号,会保留分割符
# [' runoob', '|', 'runoob', '|', 'runoob.']

如果你不想保留分割字符串到结果列表中去,但仍然需要使用到括号来分组正则表达式的话, 确保你的分组是非捕获分组,形如 (?:pattern)

回溯引用

回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式。

\0表示整个正则表达式、\1表示第一个子表达式、\2表示第二个子表达式,以此类推

this is is a apple,and and I like it it.
[ ]+(\w+)[ ]+\1     匹配重复出现两次的单词,\1表示的就是(\w+)

子表达式(分组)命名

除了使用上面\1 、\2的方式,来引用子表达式,还可以为子表达式命名,然后引用。

为子表达式命名:(?P<name> rexp)

引用子表达式:(?P=name)

例子:

匹配单、双引号包围的字符串

文本:

she said: "I love you",and he replied 'me,too'

正则表达式:(?P<quote>['"]).*?(?P=quote) 等价于(['"]).*?\1

结果:

she said: “I love you”,and he replied ‘me,too’

使用回溯替换

将电话格式进行修改:

文本:

我的电话是1234567890

搜索表达式:

(\d{3})(\d{3})(\d{4})

替换表达式:

\1***\2

结果:

我的电话是123***7890

Javascript 在搜索表达式中使用\1,在替换表达式中使用$1来获取引用

使用回溯进行大小写

不过支持该用法的不多(java不支持)

\E   end,表示大小写转换的结束范围
\l   low,表示把下一个字符转为小写
\L      Low,表示把\L与\E之间的字符转为小写
\u   up,表示把下一个字符转为大写
\U   Up,表示把\U与\E之间的字符转为大写

举例:
搜索正则表达式
<([Hh][1-6])>(.*?)</\1>
替换正则表达式
<$1>\U$2\E<$1>
注意:字母有大小写之分,文字没有。

前后查找

向前查找、向后查找实际上都是子表达式。它们查找满足条件的字符串,但忽略掉其中指定的部分(不消费)。

具体理解是:从字符串开始位置,有一个光标,光标每向前移动一次,就在光标所在的位置之前或之后判断某些字母是否符合条件,如果符合,就把光标所在位置之前的字符串返回,但忽略子表达式匹配到的字符串

区别是:

  • 向前查找:真正需要的是前面部分的字符串( http: )。
  • 向后查找:真正需要的是后面部分的字符串( $400 )。
  • 向前查找模式的长度是可变的,可以包含.和+之类的元字符
  • 向后查找模式只能是固定长度的。(个人理解为:如果不是固定长度的话,可能会把后面需要输出的部分给隐藏起来)

正向向前查找(大部分语言支持)

(?=需要匹配但不在结果中的正则表达式)

向前查找指定了一个必须匹配但不在结果中返回的模式,正向是指等于,负向指不等于,后面会讲。


例子1:

预查流程

例子2:

文本:

http://www.baidu.com
https://www.baidu.com
ftp://ftp.forta.com

正则表达式:

.+(?=:) 需要匹配到冒号,但在结果中不需要该冒号

结果:

http
https
ftp

正向向后查找

(?<=需要匹配但不在结果中的正则表达式)


例子1:

后瞻流程

例子2:

文本:

AAA:$23.45
BBB:$567.45
total items found:2

正则表达式:

(?<=\$)[0-9.]+ 需要匹配到美元$,但在结果中不需要$

结果:

23.45
567.45

向前向后结合

目标:提取出\标签内的文字

文本:

<head>
<title> ben forta's homepage </title>
</head>

正则表达式:

(?<=\<title\>).*(?=\</title\>)

结果:

 ben forta's homepage 

负向前后查找(对前后查找取非)

前面介绍的用法称为 正向前查找 和 正向后查找 ,“正”指的是寻找匹配的事实

前后查找还有一种不太常见的用法:负前后查找,“负”指的是寻找不相匹配的事实

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

使用!在进行取非

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

例子:

查找价格

文本:

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

正则表达式:(?<=\$)\d+

结果:

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

查找数量

文本:

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

正则表达式:\b(?<!\$)\d+

结果:

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

注意到这里有个\b,为什么要这样呢?看下面的结果就知道了

正则表达式:(?<!\$)\d+

结果:

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

$30中的0也被匹配上了。因为0前面的3不是\$,完全符合(?<!\$)\d+

嵌入条件

并非所有正则表达式实现都支持条件处理

正则表达式里的条件要用(?(condition)true_regx|false_regx)来定义,可以没有false_regx。

之前见过几种非常特殊的条件了:

  • .?[]?()?匹配前一个字符或者表达式
  • (?=……)(?<=……)匹配前面或后面的文本

回溯引用条件

(?(回溯引用的id)true-regx|false-regx) ,当回溯引用存在时,匹配true-regx模式,否则匹配false-regx,

例子:匹配合法的电话

文本:

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
(123)-456-7890
(123-456-7890
1234567890
123 456 7890

其中(?(1)\)|-)就是回溯引用条件,?(1)表示子表达式1存在时,进行匹配右括号),否则匹配连字符-

前后查找条件

(?(向前向后查找表达式)true-regx|false-regx)

在实际工作中,该方法相当少见,因为有更简单的方法来到达同样的目的。

例子:

文本:

11111
22222
33333-
44444-4444

正则表达式:\d{5}(?(?=-)-\d{4})

结果:

11111
22222
33333-
44444-4444

(?(?=-)-\d{4}) 中用(?=-)来进行向前匹配,如果条件成立,则-\d{4}将匹配连字符和随后的4位数字,这样33333- 将被排除在外。

总结

元字符可以分为2类:

  1. 一种是本身就是元字符,转义后变为普通字符,比如^ $ * ,用\进行转义恢复成普通字符
  2. 另一种是需要\进行配合,才表示元字符,否则就是普通的字符,比如\w \s \d

评论