正则表达式高级技巧背后的关键概念[SM]

正则表达式(Regular Expression, abbr. regex) 功能强大,能够用于在一大串字符里找到所需信息。它利用约定俗成的字符结构表达式来发生作用。不幸的是,简单的正则表达式对于一些高级运用,功能远远不够。若要进行筛选的结构比较复杂,你可能就需要用到高级正则表达式

本文为您介绍正则表达式的高级技巧。我们筛选出了八个常用的概念,并配上实例解析,每个例子都是满足某种复杂要求的简单写法。如果你对正则的基本概念尚缺乏了解,请先阅读这篇文章,或者这个教程,或者维基条目

这里的正则语法适用于PHP,与Perl兼容。

 

1. 贪婪/懒惰

所有能多次限定的正则运算符都是贪婪的。他们尽可能多地匹配目标字符串,也就是说匹配结果会尽可能地长。不幸的是,这种做法并不总是我们想要的。因此,我们添加“懒惰”限定符来解决问题。在各个贪婪运算符后添加“?”能让表达式只匹配尽可能短的长度。另外,修改器“U”也能惰化能多次限定的运算符。理解贪婪与懒惰的区别是运用高级正则表达式的基础。

贪婪操作符

操作符 * 匹配之前的表达式零次或零次以上。它是一个贪婪操作符。请看下面的例子:

preg_match( '/<h1>.*< \/h1>/', '</h1><h1>这是一个标题。</h1>
<h1>这是另一个。</h1>', $matches );

句点(.)能代表除换行符外的任意字符。上面的正则表达式匹配 h1 标签以及标签内的所有内容。它用句点(.)和星号(*)来匹配标签内的所有内容。匹配结果如下:

这是一个标题。

这是另一个。

整个字串都被返回。* 操作符会连续匹配所有内容—— 甚至包括中间的 h1 闭合标签。因为它是贪婪的,匹配整个字串是符合其利益最大化原则。

懒惰操作符

把上面的式子稍作修改,加上一个问号(?),能让表达式变懒惰:

/<h1>.*?< \/h1>/</h1>

这样它会觉得,只需匹配到第一个 h1 结尾标签就完成任务了。

另一个有着类似属性的贪婪操作符是 {n,} 。它代表之前的匹配模式重复n次或n次以上,如果没有加上问号,它会寻找尽可能多的重复次数,加上的话,则会尽可能少重复(当然也就是“重复n次”最少)。

# 建立字串
$str = 'hihihi oops hi';
# 使用贪婪的{n,}操作符进行匹配
preg_match( '/(hi){2,}/', $str, $matches );  # matches[0] 将是 'hihihi'
# 使用堕化了的 {n,}? 操作符匹配
preg_match( '/(hi){2,}?/', $str, $matches );  # matches[0] 将是 'hihi'

2. 回返引用(Back referencing)

有什么用?

回返引用(Back referencing)一般被翻译成“反向引用”、“后向引用”、“向后引用”,个人觉得“回返引用”更为贴切[笨活儿]。它是在正则表达式内部引用之前捕获到的内容的方法。例如,下面这个简单例子的目的是匹配出引号内部的内容:

# 建立匹配数组
$matches = array();
# 建立字串
$str = "\"This is a 'string'\"";
# 用正则表达式捕捉内容
preg_match( "/(\"|').*?(\"|')/", $str, $matches );
# 输出整个匹配字串
echo  $matches[0];

它会输出:

"This is a'

显然,这并不是我们想要的内容。

这个表达式从开头的双引号开始匹配,遭遇单引号之后就错误地结束了匹配。这是因为表达式里说:("|'),也就是双引号(")和单引号(')均可。要修正这个问题,你可以用到回返引用。表达式\1,\2,…,\9 是对前面已捕获到的各个子内容的编组序号,能作为对这些编组的“指针”而被引用。在此例中,第一个被匹配的引号就由1代表。

如何运用?

将上面的例子中,后面的闭合引号替换为1:

preg_match( '/("|\').*?\1/', $str, $matches );

这会正确地返回字串:

"This is a 'string'"

译注思考题:

如果是中文引号,前引号和后引号不是同一个字符,怎么办?

还记得PHP函数 preg_replace 吗?其中也有回返引用。只不过我们没有用 \1 … \9,而是用了 $1 … $9 … $n (此处任意数目均可)作为回返指针。例如,如果你想把所有的段落标签 <p> 都替换成文本:

$text = preg_replace( '/<p>(.*?)< \/p>/',
'&lt;p&gt;$1&lt;/p&gt;', $html );

参数$1是一个回返引用,代表段落标签

 

内部的文字,并插入到替换后的文本里。这种简便易用的表达式写法为我们提供了一个获取已匹配文字的简单方法,甚至在替换文本时也能使用。

3. 已命名捕获组(Named Groups)

当在一个表达式内多次用到回返引用时,很容易就会把自己弄糊涂,判断N个数字(\1 … \9)都到底代表哪一部分会比较困难。这是就可以用到带名字的捕获组(下文简称“有名组”)。有名组使用(?P<pattern>)来设定,name代表组名,pattern是配合该有名组的正则结构。请看下面的例子:

/(?P<quote>"|').*?(?P=quote)/

上式中,quote就是组名,"|'是改组匹配内容的正则。后面的(?P=quote)是在调用组名为quote的有名组。这个式子的效果和上面的回调引用实例一样,只不过是用了有名组来实现。是不是更加易读易懂了?

有名组也能用于处理已匹配内容之数组的内部数据。赋予特定正则的组名也能作为所匹配到的内容在数组内部的索引词。

preg_match( '/(?P<quote>"|\')/', "'String'", $matches );
# 下面的语句输出“'”(不包括双引号)
echo $matches[1];
# 使用组名调用,也会输出“'”
echo $matches['quote'];

所以,有名组并不只是让写代码更容易,它也能用于组织代码。

4. 字词边界(Word Boundaries)

字词边界是字串里的字词字符(包括字母、数字和下划线,自然也包括汉字)和非字词字符之间的位置。其特殊之处就在于,它并不匹配某个实在的字符。它的长度是。 \b 匹配所有字词边界。

可惜,字词边界大多数情况下都被无视了,很多人并不知道如何实际运用。 下面举个例子。比如说你想要匹配单词“import”:

/import/

注意了!正则表达式有时候很调皮的。下面的字串也能和上面的式子匹配成功:

important

你或许觉得,只要在import前后加上空格,不就可以匹配 import 这个独立的单词了:

/ import /

那如果遇上这种情况呢:

The trader voted for the import

当 import 这个词在字串开头或者结尾时,修改后的表达式仍然不能用。因此,考虑各种情况是必须的:

/(^import | import | import$)/i

别慌,还没完呢。如果遇到标点符号呢?就为了满足这一个单词的匹配,你的正则可能就需要这样写:

/(^import(:|;|,)? | import(:|;|,)? | import(\.|\?|!)?$)/i

对于只匹配一个单词来说,这样做实在是有点大动干戈了。正因如此,字词边界才显得意义重大。要满足上述要求,以及很多其他情况,利用字符边界,只需如此写:

/\bimport\b/

上面所有情况都得到了解决。\ b 的灵活性就在于,它是一个没有长度的匹配。它只匹配两个实际字符之间想象出的位置。它检查两个相邻字符是否是一个为单字,另一个为非单字。情况符合,就返回匹配。如果遇到了单词的开头或结尾, \b 会把它当成是非单词字符对待。由于import里面的 i 仍然被看成是单词字符,import 就被匹配出来了。

注意,与\b相对,我们还有\B,此操作符匹配两个单字或者两个非单字之间的位置。因此,如果你想匹配在某个单词内部的‘hi’,可以使用:

\Bhi\B

“this”、“hight”,都会返回匹配,而“hi there”则不会返回匹配。

5. 最小组团(Atomic Groups)

最小组团是无捕捉的特殊正则表达式分组。通常用来提高正则表达式的效能,也能用于消除特定匹配。一个最小组团可以用(?>pattern) 来定义,其中pattern是匹配式。

/(?>his|this)/

当正则引擎针对最小组团进行匹配时,它会跳过组团内标记的回溯位置。以单词“smashing”为例,当用上面的正则表达式匹配时,正则引擎会先尝试在“smashing”里寻找“his”。显然,找不到任何匹配。此时,最小组团就发挥作用了:正则引擎会放弃所有回溯位置。也就是说,它不会尝试再从“smashing”里查找“this”。为什么要这样设置?因为“his”都没有返回匹配结果,包含有“his”的“this”当然就更匹配不了了!

上面的例子并没有什么实用性,我们用/t?his?/ 也能达到效果。再看看下面的例子:

/\b(engineer|engrave|end)\b/

如果把“engineering”拿去匹配,正则引擎会先匹配到“engineer”,但接下来就遇到了字词边界,\b,所以匹配不成功。然后,正则引擎又会尝试在字串里寻找下一个匹配内容:engrave。匹配到eng的时候,后面的又对不上了,匹配失败。最后,尝试“end”,结果同样是失败。仔细观察,你会发现,一旦engineer匹配失败,并且都抵达了字词边界,“engrave”和“end”这两个词就已经不可能匹配成功了。这两个词都比engineer短小,正则引擎不应该再多做无谓的尝试。

/\b(?>engineer|engrave|end)\b/

上面的替代写法更能节省正则引擎的匹配时间,提高代码的工作效率。

6. 递归(Recursion)

递归(Recursion)用于匹配嵌套结构,例如括弧嵌套, (this (that)),HTML标签嵌套

 

 

 

。我们使用(?R)来代表递归过程中的子模式。下面是一个匹配嵌套括弧的例子:

/\(((?>[^()]+)|(?R))*\)/

最外层使用了反义符的括号“(”匹配嵌套结构的开端。然后是一个多选项操作符( * | * ),可能匹配除括号外的所有字符 “(?>[^()]+)”,也可能是通过子模式“(?R)”来再次匹配整个表达式。请注意,这个操作符会尽量多地匹配所有嵌套。

递归的另一个实例如下:

/<([\w]+).*?>((?>[^<>]+)|((?R)))*<\/\1>/

以上表达式综合运用了字符分组,贪婪操作符、回溯,以及最小化组团来匹配嵌套标签。第一个括弧内分组([\w]+)匹配出标签名,用于接下来的应用。若找到这尖括号样式的标签,则尝试寻找标签内容的剩余部分。下一个括弧括起来的子表达式和上一个实例非常相似:要么匹配不包括尖括号的所有字符 ?>[^<>]+,要么递归匹配整个表达式(?R)。表达式最后的<\/1>代表闭合标签。

7. 回调(Callbacks)

匹配结果中的特定内容有时可能会需要某种特别的修改。要应用多重而复杂的修改,正则表达式的回调就有了用武之地。回调是用于函数preg_replace_callback中的动态修改字串的方式。你可以为preg_replace_callback指定某个函数为参数,此函数能接收匹配结果数组为参数,并将数组修改后返回,作为替换的结果。

例如,我们想将某字串中的单词全部转换为首字母大写。十分不巧,PHP没有直接转化字母大小写的正则操作符。要完成这项任务,就可以用到正则回调。首先,我们要匹配出所有需要被大写的字母:

/\b\w/

上式同时使用了字词边界和字符类。光有这个式子还不够,我们还需要一个回调函数:

function upper_case( $matches ) {
return strtoupper( $matches[0] );
}

函数upper_case接收匹配结果数组,并将整个匹配结果转化成大写。 在此例中,$matches[0]代表需要被大写的字母。然后,我们再利用preg_replace_callback完成这次回调:

preg_replace_callback( '/\b\w/', 'upper_case', $str );

一个简单的回调即有这般强大的力量。

8. 注释(Commenting)

注释不用来匹配字串,但确实是正则表达式中最重要的部分。当正则越写越深入,越写越复杂,要推译出究竟什么东西被匹配就会变得越来越困难。在正则表达式中间加上注释,是最小化将来的迷糊和困惑的最佳方式。

要在正则表达式内部加上注释,使用(?#comment)格式。把“comment”替换成你的注释语句:

/(?#数字)\d/

如果你打算把代码公之于众,为正则表达式加上注释就显得尤为重要。这样别人才能更容易看懂和修改你的代码。和其他场合的注释一样,这样做也能为你重访自己以前写的程序时提供方便。

考虑使用“x”或“(?x)”标记位来格式化注释。这个修改器让正则引擎忽略表达式参数之间的空格。“有用的”空格仍然能够通过[ ]或\s,或者\ (反义符加空格)来匹配。

/
\d    #digit
[ ]   #space
\w+   #word
/x

上面的代码与下面的式子作用一样:

/\d(?#digit)[ ](?#space)\w+(?#word)/

请时刻注意代码的可读性。

更多资源(英文)

关于作者

Karthik Viswanathan 是一个喜欢编程和做网站的高中生。你可以到他的博客上查看他的作品:Lateral Code。你也可以关注一下他的线上Twitter应用

时间: 2024-09-27 01:53:04

正则表达式高级技巧背后的关键概念[SM]的相关文章

《深入理解Elasticsearch(原书第2版)》一1.2.2 Elasticsearch架构背后的关键概念

1.2.2 Elasticsearch架构背后的关键概念 Elasticsearch的架构遵循了一些设计理念.开发团队希望这个搜索引擎产品易于使用和扩展,这些特征在Elasticsearch的每个角落里都可以被看到.从架构的视角来看,有下面这些主要特征: 合理的默认配置,使得用户在简单安装以后能直接使用Elasticsearch而不需要任何额外的调优,这其中包括内置的发现(例如,字段类型检测)和自动配置功能. 默认的分布式工作模式.每个节点总是假定自己是某个集群的一部分或将是某个集群的一部分.对

68期:2015“双11”背后的关键技术专题

云周刊 本周要点 查看更多 [盘点]2015"双11"背后的关键技术 回首这一年,盘点技术界的大事件必然离不开"双11"这一场技术盛宴.当亿万用户购物狂欢时,屏幕那一头是众多阿里工程师的努力付出.为大家盘点了2015年"双11"阿里技术内幕,从应用服务.中间件.数据库到基础设施等方面工程师一线实战技术经验分享,让你更多了解"双11"背后的人和事,也希望帮助开发者从中得到借鉴. 阿里云Docker容器服务开发挑战与对策 阿里云2

《C Primer Plus(第6版)中文版》一2.9 关键概念

2.9 关键概念 编程是一件富有挑战性的事情.程序员要具备抽象和逻辑的思维,并谨慎地处理细节问题(编译器会强迫你注意细节问题).平时和朋友交流时,可能用错几个单词,犯一两个语法错误,或者说几句不完整的句子,但是对方能明白你想说什么.而编译器不允许这样,对它而言,几乎正确仍然是错误. 编译器不会在下面讲到的概念性问题上帮助你.因此,本书在这一章中介绍一些关键概念帮助读者弥补这部分的内容. 在本章中,读者的目标应该是理解什么是C程序.可以把程序看作是你希望计算机如何完成任务的描述.编译器负责处理一些

对人工智能一知半解?你应该知道这四个关键概念

本文讲的是对人工智能一知半解?你应该知道这四个关键概念[IT168 评论]人工智能(AI)正席卷全球,目前已有很多创新用例,而且几乎应用于所有行业.虽然说,要做出用智能机器人代替医生这种听起来就很科幻的东西,还有几十年的路要走,但现在人工智能也正为各行各业的专家们,提供着决策与解决问题方面的帮助.也会为我们消费者提供一些非常便利的功能,比如听歌识曲. 大多数人关注的是AI的一些成果性的东西,一些实际的用例.当然,还有霍金的"人工智能威胁论".但对于我来说,我更喜欢透过外表去看这台&qu

【直观梳理深度学习关键概念】优化算法、调参基本思路、正则化方式等

引言 深度学习目前已成为发展最快.最令人兴奋的机器学习领域之一,许多卓有建树的论文已经发表,而且已有很多高质量的开源深度学习框架可供使用.然而,论文通常非常简明扼要并假设读者已对深度学习有相当的理解,这使得初学者经常卡在一些概念的理解上,读论文似懂非懂,十分吃力.另一方面,即使有了简单易用的深度学习框架,如果对深度学习常见概念和基本思路不了解,面对现实任务时不知道如何设计.诊断及调试网络,最终仍会束手无策. 本系列文章旨在直观系统地梳理深度学习各领域常见概念与基本思想,使读者对深度学习的重要概念

《动手搭建智能家居系统》——第2章 关键概念

第2章 关键概念 动手搭建智能家居系统从技术的角度而言,家居自动化包括5个组成部分: 受控设备(DUC,Device Under Control)传感器和执行器控制网络控制器遥控设备

.NET正则表达式使用高级技巧之组的概念

概念|高级|技巧|正则 正则表达式中的组是很重要的一个概念,它是我们通向高级正则应用的的桥梁. 组的概念 一个正则表达式匹配结果可以分成多个部分,这就是组(Group)的目的.能够灵活的使用组后,你会发现Regex真是很方便,也很强大. 先举个例子 public static void Main() { string s = "2005-2-21"; Regex reg = new Regex(@"(?<y>\d{4})-(?<m>\d{1,2})-(

php正则表达式高级技巧及实例(Perl兼容)

正则表达式(Regular Expression, abbr. regex) 功能强大,能够用于在一大串字符里找到所需信息.它利用约定俗成的字符结构表达式来发生作用.不幸的是,简单的正则表达式对于一些高级运用,功能远远不够.若要进行筛选的结构比较复杂,你可能就需要用到高级正则表达式. 本文为您介绍正则表达式的高级技巧.我们筛选出了八个常用的概念,并配上实例解析,每个例子都是满足 某种复杂要求的简单写法.如果你对正则的基本概念尚缺乏了解,请先阅读这篇文章,或者这个教程,或者维基条目. 这里的正则语

正则表达式高级技巧及实例详解 笨活儿_正则表达式

英文原文来自Smashing Magazine.由笨活儿翻译.转载请注明出处. 正则表达式(Regular Expression, abbr. regex) 功能强大,能够用于在一大串字符里找到所需信息.它利用约定俗成的字符结构表达式来发生作用.不幸的是,简单的正则表达式对于一些高级运用,功能远远不够.若要进行筛选的结构比较复杂,你可能就需要用到高级正则表达式. 本文为您介绍正则表达式的高级技巧.我们筛选出了八个常用的概念,并配上实例解析,每个例子都是满足某种复杂要求的简单写法.如果你对正则的基