正则表达式--上

如果你有一个问题,你想到可以用正则来解决,那么你有两个问题了。

Some people, when confronted with a problem, think “I know,i’ll use regular expressions.” Now they have two problems

写正则过程

  1. 先分解

    这个问题可以分成几个子问题,是否可以用多个正则来完成(密码强度校验,一个校验大小写,一个校验数字标点,一个校验强度)

  2. 解决子问题

    某个位置上可能有多个字符?那就用字符组。某个位置上可能有多个字符串?那就用多选结构。出现的次数不确定?那就用量词。对出现的位置有要求?那就用锚点锁定位置

  3. 套皮

    也就是把如何解决子问题转化为正则表达式

  4. 调试

    复杂⼀点的正则表达式不能⼀次写对,这是很正常的

测试网页:https://regex101.com/

正则功能

graph TD
A(正则功能)
A1(校验数据的有效性)
A2(查找符合要求的文本内容)
A3(对文本进行切割,替换等操作)
A-->A1
A-->A2
A-->A3

正则元字符

graph LR
A(正则元字符)
A-->B(特殊单字符)
B-->B1(.任意字符)
B-->B2(\d 任意数字\D 任意非数字)
B-->B3(\w 任意字母数字下划线 \W任意非字母谁下划线)
B-->B4(\s任意空白符  \S 任意非空白符)
A-->C(空白符)
C-->C1(\r 回车符)
C-->C2(\n 换行符)
C-->C3(\f 换页符)
C-->C4(\t 制表符)
C-->C5(\v 垂直制表符)
C-->C6(\s 任意空白符)
A-->D(范围)
D-->D1("|或,如ab|bc 代表ab或bc")
D-->D2("[...]多选一,括号中任意单个元素")
D-->D3("[a-z]匹配a到z之间的任意单个元素(按ASCII表,包含a,z)")
D-->D4("[^...]取反,不能是括号中的任意单个元素")
A-->E(量词)
E-->E1(*含义: 0到多次)
E-->E2(+含义: 1到多次)
E-->E3(?含义: 0到1次)
E-->E4("{m}含义: 出现m次")
E-->E5("{m,}含义: 出现至少m次")
E-->E6("{m,n}含义: m到n次")
A-->F(断言)
F-->F1(单词边界)
F-->F2(行的开始/结束)
F-->F3(环视)

示例:

  1. 匹配某个网络资源:以 http:// 或者https:// 或者ftp://开头
  2. 第一位固定为1,第二位可能是3-9,第三位到第11位可以是任意数字

量词与贪婪

举例:
有字符串”aaabb”,有正则表达式”a+”,问有多少个匹配结果
有字符串”aaabb”,有正则表达式”a*”,问有多少个匹配结果

贪婪匹配(Greedy)

有字符串”aaabb”,有正则表达式”a*”,问有多少个匹配结果 的匹配过程

匹配 开始 结束 说明 匹配内容
第一次 0 3 到第一个字母b发现不满足,输出aaa aaa
第二次 3 3 匹配剩下的bb,发现匹配不上,输出空字符串 空字符串
第三次 4 4 匹配剩下的b,发现匹配不上,输出空字符串 空字符串
第四次 5 5 匹配剩下空字符串,发现匹配不上,输出空字符串 空字符串

非贪婪模式(Lazy)

有字符串”aaabb”,有正则表达式”a*?”,问有多少个匹配结果
9个结果

在python2.7中只能匹配到空串,在java8中只能匹配到空串, 在python3.7.7中可以匹配

1
2
>>> re.findall(r"a*?","aaabb")
['', 'a', '', 'a', '', 'a', '', '', '']
1
2
>>> re.findall(r"a*?","aaabb")
['', '', '', '', '', '']

示例:查找双引号之间的单词

字符串如下 “the little cat” is a toy,it looks “a little bad”

贪婪:”.*”

非贪婪:”.*?”

独占模式

一般不怎么用

示例:

we found “the little cat” is in the hat, we like “the little cat”

其中双引号中的单词看成一个单词,即the little cat 是一个单词

总结

graph LR
A("贪婪、非贪婪、独占")
A-->A1(贪婪匹配)
A1-->A11("表示次数的量词,默认是贪婪的
满足要求的情况下,尽可能按最长去匹配") A1-->A12("回溯:后面匹配不上,会吐出已匹配的再尝试") A-->A2(非贪婪匹配) A2-->A21("量词元字符后加?(英文问号),
满足要求的情况下,尽可能按最短去进行匹配") A2-->A22("回溯:后面匹配不上,会匹配更长再接着尝试") A-->A3(独占模式) A3-->A31("量词元字符后加+(英文加号)
满足要求的情况下,尽可能按最长去匹配") A3-->A32(不会发生回溯,匹配不上即失败)

分组与引用

匹配身份证号:15位或18位数字

分组与编号

括号在正则中可以用于分组,被括号括起来的部分“子表达式”会被保存成一个子组。第几个括号就是第几个分组

不保存子组

可以在括号里面使用 ?: 不保存子组。

括号嵌套

我们只需要数左括号(开括号)是第几个,就可以确定是第几个子组

假设时间格式是 2020-05-10 20:23:05 。

((\d{4})-(\d{2})-(\d{2})) ((\d{2}):(\d{2}):(\d{2}))

日期分组编号是 1,时间分组编号是 5,年月日对应的分组编号分别是 2,3,4,时分秒的分组编号分别是 6,7,8。

命名分组

由于编号得数在第几个位置,后续如果发现正则有问题,改动了括号的个数,还可能导致编号发生变化,因此一些编程语言提供了命名分组(named grouping),这样和数字相比更容易辨识,不容易出错。命名分组的格式为(?P<分组名>正则)。

分组引用

在知道了分组引用的编号 (number)后,大部分情况下,我们就可以使用 “反斜扛 + 编号”,即 \number 的方式来进行引用

分组引用在查找中使用

前面出现的单词再次出现:(\w+)\1

https://regex101.com/r/Adg1Og/4

分组引用在替换中使用

我们可以使用反向引用,在得到的结果中,去拼出来我们想要的结果

https://regex101.com/r/Adg1Og/5

graph LR
A("正则分组")
A-->A1("功能")
A1-->A11("将某部分子表达式看成一个整体,在后续查找或替换引用分组")
A-->A2("分组编号")
A2-->A21(第几个括号就是第几个分组)
A2-->A22("非捕获分组使用(?:正则)")
A2-->A23("括号嵌套只需要看左括号的序号")
A2-->A24("命名分组(?P[名称]正则)")
A-->A3("分组引用")
A3-->A31("查找:查找重复出现的部分")
A3-->A32("替换:对原有内容格式进行改写")
A-->A4("文本编辑")

匹配模式

不区分大小写模式(Case-Insensitive)

示例:如要查找单词 cat,我们并不需要关心单词是 CAT、Cat,还是 cat

reg = [Cc][Aa][Tt]

当我们把模式修饰符放在整个正则前面时,就表示整个正则表达式都是不区分大小写的。模式修饰符是通过 (? 模式标识) 的方式来表示的。 我们只需要把模式修饰符放在对应的正则前,就可以使用指定的模式了。

由于不分大小写的英文是 Case-Insensitive,那么对应的模式标识就是 I 的小写字母 i,所以不区分大小写的 cat 就可以写成 (?i)cat。

text=cat\n CAT\nCat

我们也可以用它来尝试匹配两个连续出现的 cat,如下图所示,你会发现,即便是第一个 cat 和第二个 cat 大小写不一致,也可以匹配上。

https://regex101.com/r/x1lg4P/1

如果我们想要前面匹配上的结果,和第二次重复时的大小写一致,那该怎么做呢?我们只需要用括号把**修饰符和正则 cat **部分括起来,加括号相当于作用范围的限定,让不区分大小写只作用于这个括号里的内容

https://regex101.com/r/x1lg4P/2

需要注意的是,这里正则写成了 ((?i)cat) \1,而不是 ((?i)(cat)) \1。也就是说,我们给修饰符和 cat 整体加了个括号,而原来 cat 部分的括号去掉了。如果 cat 保留原来的括号,即 ((?i)(cat)) \1,这样正则中就会有两个子组,虽然结果也是对的,但这其实没必要

如果用正则匹配,实现部分区分大小写,另一部分不区分大小写,这该如何操作呢?就比如说我现在想要,the cat 中的 the 不区分大小写,cat 区分大小写

也就是 ((?i)the) cat

https://regex101.com/r/x1lg4P/3

单行模式

单行的英文表示是 Single Line,单行模式对应的修饰符是 (?s)

https://regex101.com/r/Adg1Og/1

多行匹配模式(Multiline)

通常情况下,^匹配整个字符串的开头,$ 匹配整个字符串的结尾。多行匹配模式改变的就是 ^ 和 $ 的匹配行为

https://regex101.com/r/Adg1Og/2

多行模式的作用在于,使 ^ 和 $ 能匹配上每行的开头或结尾,我们可以使用模式修饰符号 (?m) 来指定这个模式。

https://regex101.com/r/Adg1Og/3

注释模式(Comment)

正则中注释模式是使用 (?#comment) 来表示。

graph LR
A(正则匹配模式
Match Mode) A-->A1(不区分大小写模式
Case-Insensitive) A1-->A11("作用:正则不区分英文字母的大小写") A1-->A12("修饰符:(?i)") A-->A2(点号通配模式
Dot All) A2-->A21("作用:英文的点号可以匹配任意字符,包括换行") A2-->A22("修饰符:(?s)") A-->A3(多行模式
Multiline) A3-->A31("作用:^或$默认是匹配整个字符串的开头或结尾
多行模式使得他们能匹配每行的开头或结尾") A3-->A32("修饰符:(?m)") A-->A4(注释模式
Comment) A4-->A41("作用:添加注释") A4-->A42("修饰符:(?#comment)")

问题:

提取html中head标签中的所有内容

断言

简单来说,断言是指对匹配到的文本位置有要求。比如,去查找一个单词,我们要查找 tom,但其它的单词,比如 tomorrow 中也包含了 tom。

单词边界(Word Boundary)

我们想要把下面文本中的 tom 替换成 jerry。注意一下,在文本中出现了 tomorrow 这个单词,tomorrow 也是以 tom 开头的。

tom asked me if I would go fishing with him tomorrow.

利用前面学到的知识,我们如果直接替换,会出现下面这种结果。

1
2
替换前:tom asked me if I would go fishing with him tomorrow.
替换后:jerry asked me if I would go fishing with him jerryorrow.

单词的组成一般可以用元字符 \w+ 来表示,\w 包括了大小写字母、下划线和数字(即 [A-Za-z0-9_])。那如果我们能找出单词的边界,也就是当出现了\w 表示的范围以外的字符,比如引号、空格、标点、换行等这些符号,我们就可以在正则中使用\b 来表示单词的边界。 \b 中的 b 可以理解为是边界(Boundary)这个单词的首字母。

tom \btom tom\b \btom\b
tom
tomorrow × ×
atom × ×
atomic × × ×

行的开始或结束

可以参考空白符匹配模式

环视( Look Around)

比如我们要提取六位数字的邮政编码,不能简单的写\d{6},这样的话,11位数字的手机号码也能匹配,也就是说,除了文本本身组成符合这 6 位数的规则外,这 6 位数左边或右边都不能是数字。正则是通过环视来解决这个问题的

正则 名称 含义 示例
(?<=Y) 肯定逆序环视
positive-lookbehind
左边是Y (?<=\d)th:左边是数字的th,能匹配9th
(?<!Y) 否定逆序环视
negative-lookbehind
左边不是Y (?<!\d)th:左边不是数字的th,能匹配health
(?=Y) 肯定顺序环视
positive-lookhead
右边是Y six(?=\d):右边是数字的siz,能匹配six6
(?!Y) 否定逆序环视
negative-lookhead
右边不是Y hi(?!\d):右边不是数字的hi,能匹配high

环视与子组

环视中虽然也有括号,但不会保存成子组。保存成子组的一般是匹配到的文本内容,后续用于替换等操作,而环视是表示对文本左右环境的要求,即环视只匹配位置,不匹配文本内容

graph LR
A(断言)--> A1(单词边界)
A1-->A11(\b匹配单词边界)
A-->A2(行的开始/结束)
A2-->A21("^匹配行的开始
多行模式时,可以匹配任意行开头") A2-->A22("$匹配行的结束
多行模式时,可以匹配任意行结尾") A2-->A23("\A仅匹配整个字符串的开始
不支持多行模式") A2-->A24("\Z仅匹配整个字符串结束
不支持多行模式") A-->A3(环视) A3-->A31("(?=Y)X:匹配前面是Y的X") A3-->A32("(?!Y)X:匹配前面不是Y的X") A3-->A33("X(?=Y):匹配后面是Y的X") A3-->A34("X(?!Y):匹配后面不是Y的X")

以上


正则表达式--上
https://blog.huangyuanlove.com/2020/09/24/正则表达式-上/
作者
HuangYuan_xuan
发布于
2020年9月24日
许可协议
BY HUANG兄