赞 | 0 |
VIP | 2 |
好人卡 | 27 |
积分 | 1 |
经验 | 26327 |
最后登录 | 2019-10-13 |
在线时间 | 953 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 110
- 在线时间
- 953 小时
- 注册时间
- 2007-4-25
- 帖子
- 805
|
Ruby 1.9 使用了一个效率更高、功能更强大的正则表达式引擎——Oniguruma(鬼車)。相比于 Ruby 1.8 的正则表达式引擎来说,Oniguruma 所能描述的再也不仅仅是正则语言了。这其中的一个重要因素是由于递归表达式的出现——正则语言的一大特点就是没有递归。和大多数现代化的引擎一样,Oniguruma 甚至能用来描述上下文无关语言这样的形式语言。可是,“正则表达式”这个术语仍然被各种语言持续使用了下来。可能人们对正则表达式太熟悉了,熟悉到会觉得其名称改为“形式表达式”很别扭。
本帖主要介绍一个新功能:命名捕获组。
对于能熟练使用正则表达式的朋友来说,捕获组并不陌生。未经转义的圆括号中的表达式就是一个捕获组,我们之后可以在表达式中引用这个捕获组。传统的引用方式是通过所谓的后向引用,即 \n 的形式,来引用在表达式中从左到右第 n 个出现的捕获组。
这种方式有很多弊端。首先,用户必须维护 1, 2, ..., n 这些数字。一旦发生两个捕获组换位的情况,就要把所有的它们的后向引用都改掉,这对于较长的表达式来说无疑是力气活。如果我们能给捕获组命名,那么即便它的位置变了,名字却不会变,所以无须修改其引用处。
其次,对于代码阅读者来说(把整个表达式背下来的除外),一个数字只不过是一个密码,它本身并不能让读者直接回想起任何已知事物,读者只能从头开始阅读表达式,并按出现顺序找到对应的捕获组,这属于一个简单的破译过程。如果我们给捕获组命名,那么就可以通过一两个单词来简短地描述该捕获组所接受的语言。比如:将一个捕获组命名为“alphanum”,那么读者大概就能猜到这个捕获组匹配的是“alphanumeric”的值,也就是字母或数字。
Oniguruma 命名捕获组是通过 (?<name>regex) 的语法来完成的。name 是捕获组的名称,regex 是捕获组内部的正则表达式。
- #coding: GBK
- regex = /(?<hex>[0-9a-fA-F])\+\g<hex>/
- str = '我们需要取出4+f这个子串!'
- p str[regex] # "4+f"
复制代码 如果我们在命名捕获组时配合正则表达式的“忽略空白符”选项(字面值后添加 `x' 后缀),以及在捕获组子表达式后添加 {0} 表示匹配 0 次,就可以大大地提高表达式的可读性。给一个比较复杂的例子,是我之前写的一个匹配电子邮件地址的雏形:- #coding: GBK
- # local@domain
- $pattern = %r<
- # Define subpatterns.
- (?<local> \g<local_t>|\g<quoted> ) {0}
- (?<local_t> \g<local_a>+(?:\.\g<local_a>+)* ) {0}
- (?<local_a> \g<alphanum>|[!\#$\%&'*/=?^_`{|}~-] ) {0}
- (?<alphanum> [a-zA-Z0-9] ) {0}
- (?<quoted> ".*" ) {0}
- (?<domain> \g<hostname>|\[\g<ip_addr>\] ) {0}
- (?<hostname> \g<label>(?:\.\g<label>)* ) {0}
- (?<label> \g<alphanum>+(?:-+\g<alphanum>+)* ) {0}
- (?<ip_addr> \g<zero_255>(?:\.\g<zero_255>){3} ) {0}
- (?<zero_255> \d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5] ) {0}
- # The root pattern is ...
- \g<local>@\g<domain>
- >x
复制代码 这里,由于使用的是 %r 来构建正则表达式,所以其内部可以直接写普通的注释。每个子表达式后面跟上 {0},表示这里仅仅是定义并命名一个捕获组,并不参与实际匹配。再加上最后的 `x' 后缀,表达式中的空白都会被忽略,所以可以对表达式进行缩进。这个例子就大量使用了命名捕获组,将匹配不同字符串片段的表达式分划到了不同的子表达式。这在便于维护的同时也提高了可读性,和代码模块化是一个道理。
http://szsu.wordpress.com/2010/11/13/regex_email_addr/ |
|