正则表达式的高级技巧分享

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

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

这里的正则语法适用于php,与perl兼容。

1. 贪婪/懒惰

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

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

代码如下:

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

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

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

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

1./< 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]

它会输出:

1." this is a'
显然,这并不是我们想要的内容。

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

如何运用?
将上面的例子中,后面的闭合引号替换为1:

1.preg_match( ' /(" |' ).?1/' $str $matches )
这会正确地返回字串:

1." 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是一个回调引用,代表段落标签< p> 内部的文字,并插入到替换后的文本里。这种简便易用的表达式写法为我们提供了一个获取已匹配文字的简单方法,甚至在替换文本时也能使用。

3. 已命名捕获组(named groups)
当在一个表达式内多次用到回调引用时,很容易就把事情搞混淆,要弄清那些数字(1 … 9)都代表哪一个子内容是件很麻烦的事。回调引用的一个替代方法是使用带名字的捕获组(下文简称“有名组”)。有名组使用(?p< name> pattern)来设定,name代表组名,pattern是配合该有名组的正则结构。请看下面的例子:

1./(?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”:

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

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

1./ import /
那如果遇上这种情况呢:

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

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

1./(^import(:| | )? | import(:| | )? | import(.|?|!)?$)/i
对于只匹配一个单词来说,这样做实在是有点大动干戈了。正因如此,字词边界才显得意义重大。要适应上述要求,以及很多其他情况变种,有了字符边界,我们所需写的代码只是:

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

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

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

5. 最小组团(atomic groups)

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

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

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

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

1./b(?> engineer|engrave|end)b/
上面的替代写法更能节省正则引擎的匹配时间,提高代码的工作效率。

6. 递归(recursion)

递归(recursion)用于匹配嵌套结构,例如括弧嵌套, (this (that)),html标签嵌套< div> < div> < /div> < /div> 。我们使用(?r)来代表递归过程中的子模式。下面是一个匹配嵌套括弧的例子:

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

递归的另一个实例如下:

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

7. 回调(callbacks)

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

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

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


代码如下:

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

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

1.preg_replace_callback( ' /bw/' " upper_case" $str )
一个简单的回调即有这般强大的力量。

8. 注释(commenting)

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

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

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

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

代码如下:

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

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

1./d(?#digit)[ ](?#space)w+(?#word)/
请时刻注意代码的可读性。

模式修正符
是为正则表达式增强和补充的一个功能,使用在正则之外
例子:/正则/U U就表示一个模式修正符
一下几个为php中常用的:(注意:区分大小写)
i 正则内容在匹配时候不区分大小写(默认是区分的)
m 在匹配首内容或者尾内容时候采用多行识别匹配
s 将转义回车取消是为单位匹配

x 忽略正则中的空白
A 强制从头开始匹配
D 强制$匹配尾部任何内容\n
U 禁止贪mei匹配,只跟踪到最近的一个匹配符并结束,常用在采集程序的正则表达式

(0)

相关推荐

  • 正则表达式高级学习技巧

    什么是RE? 想必各位大大在做文件查找的时侯都有使用过万用字符"*",比如说想查找在Windows目录下所有的Word文件时,你可能就会用"*.doc"这样的方式来做查找,因为"*"所代表的是任意的字符.RE所做的就是类似这样的功能,但其功能更为强大. 写程序时,常需要比对字符串是否符合特定样式,RE最主要的功能就是来描述这特定的样式,因此可以将RE视为特定样式的描述式,举个例子来说,"\w+"所代表的就是任何字母与数字所组成

  • ASP正则表达式技巧

    复制代码 代码如下: <% str = request("str") reg = request("reg") set regex = new RegExp With regex .Pattern = reg .IgnoreCase = False .Global = True End With Set match = regex.Execute(str) If match.Count > 0 Then For Each matched in match

  • javascript 正则表达式(二) 使用技巧说明

    一.字符类       概念: 将单独的直接量字符放进方括号内就可以组合成字符类(character class). 注:红色表示不是特别清楚的 [...]  --表示字符类中任意一个字符都满足 [^...]--除字符类中的任意一个字符都满足 .--除换行符(\n)和Unicode终止符之外的任意字符(不知道有什么用?) \w--(word)任何ASCII单字字符,==[a-zA-Z0-9] \W--任何非ASCII单字字符,==[^a-zA-Z0-9] \s --(space)任何Unicod

  • 写出高效率的正则表达式技巧总结

    如果纯粹是为了挑战自己的正则水平,用来实现一些特效(例如使用正则表达式计算质数.解线性方程),效率不是问题:如果所写的正则表达式只是为了满足一两次.几十次的运行,优化与否区别也不太大.但是,如果所写的正则表达式会百万次.千万次地运行,效率就是很大的问题了. 为行文方便,先定义两个概念. 误匹配:指正则表达式所匹配的内容范围超出了所需要范围,有些文本明明不符合要求,但是被所写的正则式"击中了".例如,如果使用\d{11}来匹配11位的手机号,\d{11}不单能匹配正确的手机号,它还会匹配

  • 模板引擎正则表达式调试小技巧

    基于正则表达式替换的模板引擎很容易遇上正则表达式最大回溯/递归的限制. 惰性匹配并不可怕,正常情况下模板并不会不够用,往往不会超出限制,discuz的模板引擎就大量使用了.但是因此而不去注意.不去学习,则容易书写错误并遇上问题. 当preg_*返回的是null的时候则要注意了,判断函数是is_null. 出错并不可怕,但是最好把错误都完整的输出,这样调试就很容易了. 除了输出出错原因,还要输出匹配的文本和使用的正则,这样就很容易调试了. PHP代码 复制代码 代码如下: <?php if (is

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

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

  • PHP 正则表达式的几则使用技巧

    我的PHP正则入门,是起源于网上的一篇文章,这篇文章由浅入深的阐述了PHP正则表达式使用的方法,我觉得是一个很好的入门材料,不过学成还是要 靠个人,在使用的过程中,还是会不断地忘记,因此反反复复的阅读了这篇文章有四五遍,对于其中一些比较困难的知识点,甚至要用很久才能消化,但是只要能见 坚持着看完,你会发现自己对于正则的运用能力就会显著提高. PHP正则表达式的定义: 用于描述字符排列和匹配模式的一种语法规则.它主要用于字符串的模式分割.匹配.查找及替换操作. PHP中的正则函数: PHP中有两套

  • 正则表达式匹配不包含某些字符串的技巧

    经常我们会遇到想找出不包含某个字符串的文本,程序员最容易想到的是在正则表达式里使用,^(hede)来过滤"hede"字串,但这种写法是错误的.我们可以这样写:[^hede],但这样的正则表达式完全是另外一个意思,它的意思是字符串里不能包含'h','e','d'三个但字符.那什么样的正则表达式能过滤出不包含完整"hello"字串的信息呢? 事实上,说正则表达式里不支持逆向匹配并不是百分之百的正确.就像这个问题,我们就可以使用否定式查找来模拟出逆向匹配,从而解决我们的问

  • .NET 正则表达式使用高级技巧之替换类介绍

    \d表示什么,{,5}表示什么,\[表示什么--,这里我只想提醒大家一点,为了避免和反向引用相冲突,在你用\nn表示八进制的ASCII码时,请在\后加0,就是说,\40在表示ASCII码时,请这样写\040. 替换 Regex类有一个静态的Replace方法,其实例也有一个Replace方法,这个方法很强大,因为它可以传入一个delegate,这样,你可以自定义每次捕获匹配时,如何处理捕获的内容. 以上这段代码说明了如果使用delegate MatchEvaluator 来处理正则的Match结

  • 正则表达式的高级技巧分享

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

  • Python正则表达式使用范例分享

    作为一个概念而言,正则表达式对于Python来说并不是独有的.但是,Python中的正则表达式在实际使用过程中还是有一些细小的差别. 本文是一系列关于Python正则表达式文章的其中一部分.在这个系列的第一篇文章中,我们将重点讨论如何使用Python中的正则表达式并突出Python中一些独有的特性. 我们将介绍Python中对字符串进行搜索和查找的一些方法.然后我们讲讨论如何使用分组来处理我们查找到的匹配对象的子项. 我们有兴趣使用的Python中正则表达式的模块通常叫做're'. >>>

  • Javascript高级技巧分享

    上次整理了Ajax部分,这周看完了高级技巧部分,也整理下吧. 1.类型检测 使用Object.prototype.toString.call(obj)的方式.因为无论typeof还是instanceof都无法做到精确判断变量类型. 2.安全的构造函数通常我们定义构造函数的时候,会使用类似 复制代码 代码如下: function Person(name){ this.name = name;} 然而之后如果不是去 var person = new Person("cnblogs").而是

  • iOS中CPU线程调试的高级技巧分享

    前言 最近在开发直播,发现CPU性能被打满后导致CPU降频,发热严重,然后卡顿- 为了定位这个问题我们花费了至少 3天的时间 一点一点跟踪CPU的线程代码,当遇到C++的thread的时候没有符号表,只能看见一坨对象地址,除此以外连个方法名都没有的时候真是手足无措.本篇介绍一个高级调试 方法,使用符号表和相关 指令寻踪 相关代码调用,写的不好 大佬们请轻喷.代码相关过程感谢同事 陈豪的大力支持. Talk is cheap show me the code 我们的实现思路是找到动态库的首地址调用

  • Go语言正则表达式用法实例小结【查找、匹配、替换等】

    本文实例讲述了Go语言正则表达式用法.分享给大家供大家参考,具体如下: Go语言的正则表达式使用很简单,示例代码: 复制代码 代码如下: package test import (     "fmt"     "regexp" ) func RegixBase() {     //findTest()     //findIndexTest()     //findStringTest()     //findChinesString()     //findNum

  • C语言正则表达式操作示例

    本文实例讲述了C语言正则表达式操作.分享给大家供大家参考,具体如下: #include <stdio.h> #include <sys/types.h> #include <regex.h> int main(int argc,char**argv) { int status; int i; int cflags = REG_EXTENDED; regmatch_t pmatch[1]; const size_t nmatch =1 ; regex_t reg; con

  • JSP中正则表达式用法实例

    本文实例讲述了JSP中正则表达式用法.分享给大家供大家参考,具体如下: <%@ page language="java" import="java.util.*,cn.com.Person,cn.com.Adddress" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+

  • python实现的正则表达式功能入门教程【经典】

    本文讲述了python实现的正则表达式功能.分享给大家供大家参考,具体如下: 前文: 首先,什么叫正则表达式(Regular Expression)? 例如我们要判断字符串"adi_e32fv,Ls"里面是否含有子串"e32f",又例如我们在一个含百万个姓名的txt文件中找姓"王",名字以"五"结尾的名字,然后打印出来.结果为:"王五"."王小五"."王大五".&qu

随机推荐