github上有一个学习regex的仓库,star数比较高,里面的介绍比较简洁,把正则表达式最实用的内容概括了
https://github.com/ziishaned/learn-regex
正则表达式,之前也学习过大部分内容了,关于这个仓库的文档部分我只有零宽度断言和非捕获簇没有直接用过了,不过今天碰到了使用场景.
所以顺便学习了.
零宽度断言(前后预查)
先行断言和后发断言都属于非捕获簇(不捕获文本 ,也不针对组合计进行计数)。 先行断言用于判断所匹配的格式是否在另一个确定的格式之前,匹配结果不包含该确定格式(仅作为约束)。
例如,我们想要获得所有跟在 $
符号后的数字,我们可以使用正后发断言 (?<=\$)[0-9\.]*
。 这个表达式匹配 $
开头,之后跟着 0,1,2,3,4,5,6,7,8,9,.
这些字符可以出现大于等于 0 次。
零宽度断言如下:
符号 | 描述 |
---|---|
?= | 正先行断言-存在 |
?! | 负先行断言-排除 |
?<= | 正后发断言-存在 |
?<! | 负后发断言-排除 |
?=...
正先行断言
?=...
正先行断言,表示第一部分表达式之后必须跟着 ?=...
定义的表达式。
返回结果只包含满足匹配条件的第一部分表达式。 定义一个正先行断言要使用 ()
。在括号内部使用一个问号和等号: (?=...)
。
正先行断言的内容写在括号中的等号后面。 例如,表达式 (T|t)he(?=\sfat)
匹配 The
和 the
,在括号中我们又定义了正先行断言 (?=\sfat)
,即 The
和 the
后面紧跟着 (空格)fat
。
"(T|t)he(?=\sfat)" => The fat cat sat on the mat.
?!...
负先行断言
负先行断言 ?!
用于筛选所有匹配结果,筛选条件为 其后不跟随着断言中定义的格式。 正先行断言
定义和 负先行断言
一样,区别就是 =
替换成 !
也就是 (?!...)
。
表达式 (T|t)he(?!\sfat)
匹配 The
和 the
,且其后不跟着 (空格)fat
。
"(T|t)he(?!\sfat)" => The fat cat sat on the mat.
?<= ...
正后发断言
正后发断言 记作(?<=...)
用于筛选所有匹配结果,筛选条件为 其前跟随着断言中定义的格式。 例如,表达式 (?<=(T|t)he\s)(fat|mat)
匹配 fat
和 mat
,且其前跟着 The
或 the
。
"(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat.
?<!...
负后发断言
负后发断言 记作 (?<!...)
用于筛选所有匹配结果,筛选条件为 其前不跟随着断言中定义的格式。 例如,表达式 (?<!(T|t)he\s)(cat)
匹配 cat
,且其前不跟着 The
或 the
。
"(?<!(T|t)he\s)(cat)" => The cat sat on cat.
匹配markdown内容的例子
我要匹配的是markdown的文章部分,把一个markdown文档每个二级标题的内容抽取出来,然后给里面的图片链接重命名为二级标题相关的(因为你写文章引用到本地的图片我之前都是粗暴复制粘贴,所以图片的文件名都是乱七八糟的,而且还有些图片是没有使用的).
我要匹配的内容如下所示.
## 012.一个markdown标题
内容
最终我会把标题##
后面的内容用于图片的重命名
我的处理步骤是这样的
- 匹配章节内容包括标题
- 提取每章的标题,和原来图片标签的内容,重命名图片,并且把每章内容的图片链接用replace替换
- 写入替换后的文章
最终我对章节的捕获是这样的
chapterPattern = re.compile('((?:^.*?|\n)##[\s]+(\d+)?.*?(?=\n##\s|$))', re.S)
这是python的代码
章节头部匹配
由于python的后发断言只支持给定宽度的,可以说是很弱了,所以我捕获内容没有用后发断言
标题的特征是行首,也就是说,##
的前面的字符有两种可能,一是换行符,二是字符串的开头,所以开头匹配用((?:^.*?|\n)##
这里使用了一个不捕获簇,括号内?:
开头,后面匹配的内容就不会被捕获了,减少不必要的捕获可以提高正则的执行效率
章节尾部匹配
使用零宽度断言的好处是:他不会占用匹配内容,也就是说,如果你使用一个findall函数来寻找所有的匹配项,比如我这里尾部就用到了
(?=\n##\s|$))
,使用正先行断言,需要用括号括起来,这部分不会被匹配,这样他就可以参加后续的匹配.
我这个正则就是匹配一个章节的末尾, 因为文章是按照##
标题进行分隔的,每个标题对应一个章节. 如果你不用正先行断言,那么这次匹配过标题##
后,之后只会从##
后面的内容开始匹配,等于凭空少了一半的章节.
而如果你使用正先行断言,会在匹配完正先行断言的内容之后,从断言部分的开头再开始匹配.
结尾的特征有两种一种是普通的文章末尾,另一种是下一个标题的前面.
章节中部匹配
[\s]+(\d+).*?
中部也比较好理解,标准markdown语法标题##
后面一定要跟空格所以用[\s],表示空白字符
## 012.一个markdown标题
内容
## 013.
后续使用.*?
匹配到章节末尾为止,其中后面开了re.S
标志位, 作用是使 . 匹配包括换行在内的所有字符,这个比较强大了,.*?
最后的问号是关闭贪婪模式,这样在匹配到后面的第一个换行就会停止了,re.S和关闭贪婪模式搭配使用就比较舒服了.
这样我们执行这个正则findall之后,就能匹配出章节了
标题内容匹配
chapternamePattern = re.compile(r'##\s+(?:\d+)?\.?([^\n]*?)\n', re.S)
标题内容的开头,比较简单明了,只是我有可能会在数字标题和文字之间加一个.
,所以用\.?
匹配一下,让这个点不要补签在后续的匹配里.
后面只要匹配到第一个换行符为止就行了.
图片内容匹配
markdown中图片链接的形式是这样的
![图片名称](图片路径)
首先开头用转义字符匹配感叹号,之后匹配[]
也需要转义,其实[]
的里面也可以是[]
,可以无线套,接下来匹配的应该是图片的括号,所以
picPattern = re.compile(r'(!\[(.*?)\]\((.*?\.(?:webp|jpg|png|jpeg))\))', re.S)
最后匹配括号内要有图片链接,所以是以.webp,.png,*jpg等格式结尾
网友评论