正则表达式高级技巧背后的关键概念
[ 2009-11-19 21:07:46 | 作者: 游仰泳的金鱼 ]
正则表达式(Regular Expression, abbr. regex) 功能强大,能够用于在一大串字符里找到所需信息。它利用约定俗成的字符结构表达式来发生作用。不幸的是,简单的正则表达式对于一些高级运用,功能远远不够。若要进行筛选的结构比较复杂,你可能就需要用到高级正则表达式。
本文为您介绍正则表达式的高级技巧。我们筛选出了八个常用的概念,并配上实例解析,每个例子都是满足某种复杂要求的简单写法。如果你对正则的基本概念尚缺乏了解,请先阅读这篇文章,或者这个教程,或者维基条目。
这里的正则语法适用于PHP,与Perl兼容。
1. 贪婪/懒惰
所有能多次限定的正则运算符都是贪婪的。他们尽可能多地匹配目标字符串,也就是说匹配结果会尽可能地长。不幸的是,这种做法并不总是我们想要的。因此,我们添加“懒惰”限定符来解决问题。在各个贪婪运算符后添加“?”能让表达式只匹配尽可能短的长度。另外,修改器“U”也能惰化能多次限定的运算符。理解贪婪与懒惰的区别是运用高级正则表达式的基础。
贪婪操作符
操作符 * 匹配之前的表达式零次或零次以上。它是一个贪婪操作符。请看下面的例子:
引用
preg_match( '/<h1>.*< \/h1>/', '</h1><h1>这是一个标题。</h1>
<h1>这是另一个。</h1>', $matches );
<h1>这是另一个。</h1>', $matches );
引用
<h1>这是一个标题。</h1>
<h1>这是另一个。</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'
$str = 'hihihi oops hi';
# 使用贪婪的{n,}操作符进行匹配
preg_match( '/(hi){2,}/', $str, $matches ); # matches[0] 将是 'hihihi'
# 使用堕化了的 {n,}? 操作符匹配
preg_match( '/(hi){2,}?/', $str, $matches ); # matches[0] 将是 'hihi'
有什么用?
回返引用(Back referencing)一般被翻译成“反向引用”、“后向引用”、“向后引用”,个人觉得“回返引用”更为贴切[笨活儿]。它是在正则表达式内部引用之前捕获到的内容的方法。例如,下面这个简单例子的目的是匹配出引号内部的内容:
引用
# 建立匹配数组
$matches = array();
# 建立字串
$str = "\"This is a 'string'\"";
# 用正则表达式捕捉内容
preg_match( "/(\"|').*?(\"|')/", $str, $matches );
# 输出整个匹配字串
echo $matches[0];
$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 (此处任意数目均可)作为回返指针。例如,如果你想把所有的段落标签都替换成文本:
引用
$text = preg_replace( '/<p>(.*?)< \/p>/',
'<p>$1</p>', $html );
'<p>$1</p>', $html );
3. 已命名捕获组(Named Groups)
当在一个表达式内多次用到回调引用时,很容易就把事情搞混淆,要弄清那些数字(\1 … \9)都代表哪一个子内容是件很麻烦的事。回调引用的一个替代方法是使用带名字的捕获组(下文简称“有名组”)。有名组使用(?Ppattern)来设定,name代表组名,pattern是配合该有名组的正则结构。请看下面的例子:
引用
/(?P<quote>"|').*?(?P=quote)/
有名组也能用于处理已匹配内容之数组的内部数据。赋予特定正则的组名也能作为所匹配到的内容在数组内部的索引词。
引用
preg_match( '/(?P<quote>"|\')/', "'String'", $matches );
# 下面的语句输出“'”(不包括双引号)
echo $matches[1];
# 使用组名调用,也会输出“'”
echo $matches['quote'];
# 下面的语句输出“'”(不包括双引号)
echo $matches[1];
# 使用组名调用,也会输出“'”
echo $matches['quote'];
4. 字词边界(Word Boundaries)
字词边界是字串里的字词字符(包括字母、数字和下划线,自然也包括汉字)和非字词字符之间的位置。其特殊之处就在于,它并不匹配某个实在的字符。它的长度是零。 \b 匹配所有字词边界。
不幸的是,字词边界一般都被忽视掉了,大部分人都没有在意他的现实意义。 例如,如果你想要匹配单词“import”:
引用
/import/
引用
important
引用
/ import /
引用
The trader voted for the import
引用
/(^import | import | import$)/i
引用
/(^import(:|;|,)? | import(:|;|,)? | import(\.|\?|!)?$)/i
引用
/\bimport\b/
注意,与\b相对,我们还有\B,此操作符匹配两个单字或者两个非单字之间的位置。因此,如果你想匹配在某个单词内部的‘hi’,可以使用:
引用
\Bhi\B
5. 最小组团(Atomic Groups)
最小组团是无捕捉的特殊正则表达式分组。通常用来提高正则表达式的效能,也能用于消除特定匹配。一个最小组团可以用(?>pattern) 来定义,其中pattern是匹配式。
引用
/(?>his|this)/
上面的例子并没有什么实用性,我们用/t?his?/ 也能达到效果。再看看下面的例子:
引用
/\b(engineer|engrave|end)\b/
引用
/\b(?>engineer|engrave|end)\b/
6. 递归(Recursion)
递归(Recursion)用于匹配嵌套结构,例如括弧嵌套, (this (that)),HTML标签嵌套
。我们使用(?R)来代表递归过程中的子模式。下面是一个匹配嵌套括弧的例子:
引用
/\(((?>[^()]+)|(?R))*\)/
递归的另一个实例如下:
引用
/< ([\w]+).*?>((?>[^<>]+)|((?R)))*< \/\1>/
7. 回调(Callbacks)
匹配结果中的特定内容有时可能会需要某种特别的修改。要应用多重而复杂的修改,正则表达式的回调就有了用武之地。回调是用于函数preg_replace_callback中的动态修改字串的方式。你可以为preg_replace_callback指定某个函数为参数,此函数能接收匹配结果数组为参数,并将数组修改后返回,作为替换的结果。
例如,我们想将某字串中的字母全部转变成大写。十分不巧,PHP没有直接转化字母大小写的正则操作符。要完成这项任务,就可以用到正则回调。首先,表达式要匹配出所有需要被大写的字母:
引用
/\b\w/
引用
function upper_case( $matches ) {
return strtoupper( $matches[0] );
}
return strtoupper( $matches[0] );
}
引用
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
/x
引用
/\d(?#digit)[ ](?#space)\w+(?#word)/
评论Feed: http://www.idcfish.com/blog/feed.asp?q=comment&id=375
这篇日志没有评论.






