- 浏览: 20769 次
- 性别:
- 来自: 苏州
最新评论
原文作者:Karthik Viswanathan
原文链接:Crucial Concepts Behind Advanced Regular Expressions
译者:笨活儿
英文原文来自Smashing Magazine。由笨活儿翻译。转载请注明出处。
正则表达式(Regular Expression, abbr. regex) 功能强大,能够用于在一大串字符里找到所需信息。它利用约定俗成的字符结构表达式来发生作用。不幸的是,简单的正则表达式对于一些高级运用,功能远远不够。若要进行筛选的结构比较复杂,你可能就需要用到高级正则表达式。
本文为您介绍正则表达式的高级技巧。我们筛选出了八个常用的概念,并配上实例解析,每个例子都是满足某种复杂要求的简单写法。如果你对正则的基本概念尚缺乏了解,请先阅读这篇文章,或者这个教程,或者维基条目。
这里的正则语法适用于PHP,与Perl兼容。
1. 贪婪/懒惰
所有能多次限定的正则运算符都是贪婪的。他们尽可能多地匹配目标字符串,也就是说匹配结果会尽可能地长。不幸的是,这种做法并不总是我们想要的。因此,我们添加“懒惰”限定符来解决问题。在各个贪婪运算符后添加“?”能让表达式只匹配尽可能短的长度。另外,修改器“U”也能惰化能多次限定的运算符。理解贪婪与懒惰的区别是运用高级正则表达式的基础。
贪婪操作符
操作符 * 匹配之前的表达式零次或零次以上。它是一个贪婪操作符。请看下面的例子:
preg_match( '/<h1>.*<\/h1>/', '<h1>这是一个标题。</h1> <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'
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>/', "<p>$1</p>", $html );
参数$1是一个回返引用,代表段落标签<p>
内部的文字,并插入到替换后的文本里。这种简便易用的表达式写法为我们提供了一个获取已匹配文字的简单方法,甚至在替换文本时也能使用。
3. 已命名捕获组(Named Groups)
当在一个表达式内多次用到回调引用时,很容易就把事情搞混淆,要弄清那些数字(1 … 9)都代表哪一个子内容是件很麻烦的事。回调引用的一个替代方法是使用带名字的捕获组(下文简称“有名组”)。有名组使用(?P<name>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 /
那如果遇上这种情况呢:
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标签嵌套<div>
<div></div>
</div>
。我们使用(?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)”修改器来格式化注释。这个修改器让正则引擎忽略表达式参数之间的空格。“有用的”空格仍然能够通过[ ]
或\
(反义符加空格)来匹配。
/ \d #digit [ ] #space \w+ #word /x
上面的代码与下面的式子作用一样:
/\d(?#digit)[ ](?#space)\w+(?#word)/
请时刻注意代码的可读性。
更多资源(英文)
- Regular-Expressions.info Comprehensive website on regular expressions
- Cheat SheetInformative regular expressions cheat sheet
- Regex GeneratorJavaScript regular expressions generator
发表评论
-
java src for mac pro
2011-08-13 16:00 9741. 从connect.apple.com下载java d ... -
做大的艺术 - 大型网站的架构设计
2009-11-03 14:37 885如果说1980年代是PC的时 ... -
项目中你不得不知的11个Java第三方类库
2009-11-02 13:22 777项目中你不得不知的11个Java第三方类库 ... -
把大象装冰箱,需要分几步?- - -迅雷产品经理笔试、面试回顾
2009-10-22 13:18 1378把大象装冰箱,需要分几步?- - -迅雷产品经理笔试、面试 ... -
大道至简-Java之23种模式一点就通
2009-04-16 22:15 669FACTORY—人才市场:以往 ... -
Dom4j的使用(全而好的文章)
2009-04-14 13:20 676Dom4j的使用(全而好的文章) 关键字: dom4 ... -
各大型网站架构分析收集收藏
2009-02-08 21:50 9711. PlentyOfFish 网站架构学 ... -
Spring中使用classpath加载配置文件浅析
2009-01-03 22:45 938Spring中使用classpath加载配置文件浅析 ... -
Log4j基本使用方法
2008-12-28 21:48 824Log4j由三个重要的组 ... -
Log4j中文使用手册
2008-12-28 21:46 4752Copyright&copy; 2000-20 ...
相关推荐
### Oracle正则表达式详解(用法+实例) #### 一、正则表达式简介 正则表达式是一种用于匹配字符串中字符组合的工具。它由一个或多个字符及特殊的字符元组成,能够帮助我们执行复杂的字符串搜索和替换任务。在...
- **学生和教育工作者**:作为教学材料,本书适合教授正则表达式的基本概念和高级技巧。 #### 五、结语 《正则表达式经典实例》是一本非常实用的指南,不仅为读者提供了丰富的知识和技巧,还通过具体的实例帮助...
Java正则表达式是Java编程语言中用于处理字符串的强大工具,它基于模式匹配的概念,能够高效地进行文本搜索、替换和解析。在Java中,正则表达式主要通过`java.util.regex`包来实现,提供了Pattern和Matcher两个核心...
### 正则表达式使用详解及使用实例 #### 一、正则表达式的概念与应用场景 正则表达式(Regular Expression)是一种强大的模式匹配工具,它允许用户通过一系列特殊的字符来构建复杂的匹配模式。这些模式可以用来...
- 本书"正则表达式详细文档CHM版"提供了深入的理论和实例,帮助读者掌握正则表达式的核心概念和技巧。 - 其他学习资料,如《Mastering Regular Expressions》、《正则表达式30分钟入门教程》等,也是学习正则...
### 正则表达式详解 #### 一、正则表达式概述 正则表达式是一种强大的文本模式匹配工具,广泛应用于编程语言如Java中,用于处理字符串数据,实现字符串的搜索、替换、提取等操作。它能够精确描述复杂的文本规则,...
二、正则表达式语法详解 三、正则表达式的匹配原理 四、正则表达式的常用函数与工具 五、正则表达式的应用实例 六、正则表达式的优化与调试 一、正则表达式基础 重点详细内容知识点总结 1.正则表达式的定义:正则...
### js正则表达式详解 #### 一、正则表达式基础知识 正则表达式是一种强大的文本处理工具,被广泛应用于各种编程语言中,...掌握好正则表达式的基本概念和用法,对于提高开发效率和数据处理能力都有着重要的作用。
### 正则表达式基础知识与应用详解 #### 一、正则表达式简介 正则表达式(Regular Expression)是一种强大的文本处理工具,能够帮助我们完成...希望本文能够帮助读者更好地理解和掌握正则表达式的核心概念和使用技巧。
GREP正则表达式的基本概念 ##### 2.1 正则表达式基础 - **元字符**:元字符是一些具有特殊含义的字符,例如`.`、`*`、`+`等,它们可以帮助构建更复杂的匹配规则。 - **锚点**:用于指定匹配的起始或结束位置,如`...
### 报表工具FineReport正则表达式定义规则 #### 一、正则表达式概述 正则表达式是一种强大的文本处理...此外,随着技术的发展,正则表达式的功能也在不断扩展和完善,因此持续学习新的特性和技巧也是十分重要的。
例如,创建一个简单的正则表达式实例,可以使用如下代码: ```csharp Regex r = new Regex("\\s2000"); // 定义一个正则表达式 ``` 其中,`\s` 是一个特殊字符,表示空白字符,而 `\\s` 则表示匹配一个字面值的...
标题“30分钟搞定正则表达式(全部实例)”意味着本文将通过一系列实用的示例,帮助读者在短时间内迅速掌握正则表达式的使用技巧。正则表达式是一种强大的文本处理工具,用于模式匹配、搜索、替换等操作,广泛应用于...
### Java正则表达式详解 #### 一、正则表达式的重要性及应用 正则表达式,作为一种强大的文本处理工具,对于程序员而言是不可或缺的基本技能之一。它在文本的匹配、搜索和替换等方面发挥着不可替代的作用。尤其在...
学习正则表达式时,PDF教程通常包含详细的解释、实例和练习,帮助读者深入理解正则表达式的语法和用法。你可以通过阅读这些教程来进一步提高自己的正则表达式技能。 5. **环境配置**: - 本示例提到的环境是`...
本文为您介绍正则表达式的高级技巧。我们筛选出了八个常用的概念,并配上实例解析,每个例子都是满足某种复杂要求的简单写法。如果你对正则的基本概念尚缺乏了解,请先阅读 这篇文章,或者 这个教程,或者维基条目。...
三、Java正则表达式实例 下面的Java代码示例展示了如何使用正则表达式进行匹配和替换: ```java import java.util.regex.*; public class Demo4Regex { public static void main(String[] args) { // 创建...
### 正则表达式通用方法详解 #### 方法定义 ```java public static List, String>> getRegResult(String content, String regX) ``` 这是一个静态方法`getRegResult`,接收两个参数:`content`表示要进行正则匹配的...