原创作品,转载请标明出处。@周荣华

说说正则表达式

1 引言

网上说正则式的文章很多,刚开始有同事提议写写正则式,我实际上是有点拒绝的,毕竟看看别人的文章基本上就能满足需求了,纯粹做搬运工有点心有不甘,但要写的有新意确实也很困难。

但回想起自己刚接触正则表达式时的窘境,也看到csdn上还有一些没什么油盐的正则表达式文章居然还开收费,觉得还是有一些可说的。

2 正则表达式的使用场景

正则表达式,英文全称是regular expression或者rational expression,从字面意思看regular表示常规的,合规的,正常的等含义;rational表示合理的,理性的等含义。

这个概念起源于数学家Stephen Cole Kleene1951年在《神经网络和有限自动机的事件表达》定义了一种新的数学描述语言regular events,该文章中引入了EvF, EF, E*F等表达式,这也算是正则表达式的雏形。

常规使用正则式的场景主要有编程语言、命令行、文本编辑器这三种。

2.1 编程语言

编程语言不用多说,为了实现某个功能,功能里面可能就要求支持正则匹配进行搜索和替换。

基本上大家现在还在使用的高级语言都支持正则表达式,部分是编程语言原生就支持,部分需要使用第三方的类库方式来支持。

例如:python,bash,c++,java,php,perl等。

2.2 命令行界面

这个和编程语言类似,主要是各种shell,和编程语言的差别无非是即写即用。shell可以认为是一个字符流的执行实体,这个字符流中任意一个全集或者子集都可以应用到正则表达式。里面使用正则表达式最突出的是linux的三件套,grep、sed和awk。

2.3 各种编辑器

很多文本编辑器的搜索和替换,都支持正则表达式(很遗憾,office还不支持正则表达式),例如常用的notepad++,sublime text,vscode等等。

在处理一个文本文件的时候,会点正则表达式经常会有事半功倍的效果。

举个例子,很多web页面对右键事件和选择事件做了捕获,这使得正常的拷贝动作,即使是文本拷贝,在对应网站上没法实现,用开发者工具类似的功能能看到web的源代码,并将对应段的html代码拷贝出来,但html代码中有大量的tag,会影响我们获取纯文本,使用下面的正则表达式可以匹配所有tag,替换成空就可以达到保留纯文本的目的:

<[^>]*>

 

3 正则表达式的风格

现在大家使用的正则表达式已经不是Stephen Cole Kleene定义的最原始版本的regular events了,比较普遍的主要有两种风格,POSIX和PCRE。

3.1 POSIX Extended 1003.2

和网络协议栈里面的ISO和TCP/IP对应,正则表达式也存在一个国际标准和一个事实标准。

POSIX Extended 1003.2是电气和电子工程师协会(IEEE)制定的,相当于国际标准,但实际上各种编程语言对它的支持并不好,或者有些是部分支持。Bash默认的是POSIX风格的正则表达式,但部分命令,例如grep可以使用-E(POSIX Extended 1003.2),-G(BRE)和-P(PCRE)来指定不同风格的正则表达式。

3.2 PCRE

PCRE相当于事实标准,基本上绝大多数编程语言都支持PCRE,当然最早在编程语言中支持正则表达式的Perl更是因为PCRE在文本处理中一骑绝尘,很多后起的编程语言,都依赖Perl的相关设计来指定自己的文本处理规则。

Python编程语言和notepad++/sublime text这两种编辑器是perl风格的正则表达式。有一些编程语言,例如PHP,2种风格都支持。

 

3.3 其他风格

由于POSIX Extended 1003.2没有对多字节字符的说明,PHP做POSIX标准(ereg)的基础上,还支持了多字节字符的POSIX标准(mb_ereg)。

PCRE依赖修正符u来支持UTF-8格式的正则表达式。

 

3.4 POSIX Extended 1003.2和PCRE的差异

3.4.1 定界符

POSIX Extended 1003.2没有定界符,PCRE有定界符,并且除了字母、数字和反斜线\以外的任何字符都可以做定界符。

为什么POSIX Extended 1003.2没有定界符?

这要从PCRE为什么有定界符来说起,PCRE引入定界符主要是为了给修正符一个合适的位置,也就是说一对定界符包围的字符串之外的字符就是修正符,POSIX Extended 1003.2不支持修正符,所以也没有必要支持定界符。

3.4.2 修正符

PCRE中支持11种修正符,方便正则表达式的使用更加灵活:

i(忽略大小写),m(多行修正),s(.包含换行符),x(忽略空白字符,转义的空白除外),e(支持逆向引用),A(强制从开头开始匹配),D($匹配换行符,m设置的话该参数无效),S(加速匹配),U(?使用贪婪模式),X(待扩展),u(默认UTF-8)

3.4.3 POSIX Extended 1003.2的类型匹配

[:upper:]:匹配所有的大写字母

[:lower:]:匹配所有的小写字母

[:alpha:]:匹配所有的字母

[:alnum:]:匹配所有的字母和数字

[:digit:]:匹配所有的数字

[:xdigit:]:匹配所有的十六进制字符,等价于[0-9A-Fa-f]

[:punct:]:匹配所有的标点符号,等价于[.,"'?!;:]

[:blank:]:匹配空格和TAB,等价于[ \t]

[:space:]:匹配所有的空白字符,等价于[ \t\n\r\f\v]

[:cntrl:]:匹配所有ASCII 0到31之间的控制符。

[:graph:]:匹配所有的可打印字符,等价于:[^ \t\n\r\f\v]

[:print:]:匹配所有的可打印字符和空格,等价于:[^\t\n\r\f\v]

[.c.]:未定义

[=c=]:未定义

[:<:]:匹配单词的开始

[:>:]:匹配单词的结尾

3.4.4 PCRE的类型匹配

\a alarm,即 BEL字符(’0)

\cx "control-x",其中 x 是任意字符

\e escape(’0B)

\f 换页符 formfeed(’0C)

\n 换行符 newline(’0A)

\r 回车符 carriage return(’0D)

\t 制表符 tab(’0)

\xhh 十六进制代码为 hh 的字符

\ddd 八进制代码为 ddd的字符,或 backreference

\d 任一十进制数字

\D 任一非十进制数的字符

\s 任一空白字符

\S 任一非空白字符

\w 任一数字、字母或下划线的字符

\W 任一非数字、字母或下划线的字符

\b 字分界线

\B 非字分界线

\A 目标的开头(独立于多行模式)

\Z 目标的结尾或位于结尾的换行符前(独立于多行模式)

\z 目标的结尾(独立于多行模式)

\G 目标中的第一个匹配位置

4 一个复杂模式匹配的替换过程

常规的查找相对都比较简单,只要归纳总结一下字符流的规律就可以形成匹配的正则表达式,常规的提取和替换也支持对第几个匹配项的提取,这在模式匹配中也非常常用。

 

如果通过某种通配模式匹配下来的字符经过一定复杂处理之后再进行替换怎么处理?一些编程语言还提供了回调的方式进行替换。

例如python手册re — Regular expression operations — Python 3.9.7 documentation里面的这个re模块的例子:

>>> import re
>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-'return ' '
...     elsereturn '-'
...
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub('-{1,}', dashrepl, 'pro----gram-files')
'pro-gram files'

其中dashrepl定义了一个回调函数,对于使用'-{1,2}'匹配一个或者两个-的情况下,分别针对单个-或者两个-的情况替换为空或者-;而在使用'-{1,}'匹配一个或者多个-的情况下,分别针对单个-或者多个-的情况替换为空或者-。

 

5 参考资料

5.1  posix和perl标准的正则表达式区别_lcy_ltpsr的专栏-CSDN博客_posix和perl标准的正则表达式区别

5.2 Representation of Events in Nerve Nets and Finite Automata | RAND

5.3 re — Regular expression operations — Python 3.9.7 documentation

posted @ 2021-09-02 18:48  周荣华  阅读(771)  评论(0编辑  收藏  举报