正则表达式

下面就来讲讲ruby支持的正则表达式符号(元字符)。

有这么个规则:

下文中出现的“匹配多字节字符的正则表达式”是指,通过使用$KCODE进行设定,或显式地使用汉字选项(请参考正则表达式字面值)等方式进行的匹配多字节字符的正则表达式。

后方参考

正则表达式 \1 \2 ... \n 表示后方参考。\n表示将匹配第n个括号(正则表达式的()表示群)的内容保存起来,供后面使用。

/((foo)bar)\1\2/

/((foo)bar)foobarfoo/

是一样的。

例:

re = /(foo|bar|baz)\1/
p re =~ 'foofoo'   # => 0
p re =~ 'barbar'   # => 0
p re =~ 'bazbaz'   # => 0
p re =~ 'foobar'   # => nil

对应的括号必须位于后方参考表达式的左侧。

若后方参考表达式位于对应的括号中时,匹配常常会失败。当后方参考表达式中的数字是1位,且没有对应的括号时,匹配也将失败。

p /(\1)/ =~ "foofoofoo" # => nil
p /(foo)\2/ =~ "foo\2"  # => nil

虽然可以指定2位以上的后方参考表达式,但是不要把它同反斜线表示法的\nnn(对应于8进制数nnn的字符)混为一谈。当数字只有1位时,通常是后方参考表达式。当指定了一个超过2位的数字时,若没有对应括号的话,则被看作是8进制代码。

相反地,若在正则表达式中使用1位的8进制代码时,必须以0打头,例如\01等(不可能存在形如\0这样的后方参考表达式,因此不会混淆)。

p   /\1/ =~ "\1"   # => nil     # 无对应括号的后方参考
p  /\01/ =~ "\1"   # => 0       8 进制代码
p  /\11/ =~ "\11"  # => 0       8 进制代码

# 8 进制代码 (因为没有对应括号)
p /(.)\10/ =~ "1\10" # => 0

# 后方参考 (因为有对应的括号)
p /((((((((((.))))))))))\10/ =~ "aa"  # => 0

# 8 进制代码 (因为没有像"\0" + "8" -> \08 这样的8进制代码)
p /(.)\08/ =~ "1\0008" # => 0

# 如果想在后方参考表达式之后插入数字的话,就必须使用括号加以分隔。
p /(.)(\1)1/ =~ "111"   # => 0

字符范围

正则表达式 [] 负责指定字符范围。这将匹配 [] 内列出的任何一个字符。

例如/[abc]/表示只要匹配"a", "b", "c"中任何一个即可。也可以按照ASCII代码顺序,在连续的字符串之间插入“-”后写成/[a-c]/也是一样的效果。另外,若头上是“^”的话,表示要匹配指定字符之外的一个字符。

若“^”不在头上的话,表示匹配该字符本身。同时,当“-”出现在头或尾上时,表示匹配该字符本身。

p /[a^]/ =~ "^"   # => 0
p /[-a]/ =~ "-"   # => 0
p /[a-]/ =~ "-"   # => 0
p /[-]/ =~ "-"   # => 0

空的字符范围将引发错误。

p /[]/ =~ ""
p /[^]/ =~ "^"
# => invalid regular expression; empty character class: /[^]/

当“]”出现在头上(或否定的“^”之后)时,表示“]”本身,而并非字符范围的结尾。

p /[]]/ =~ "]"       # => 0
p /[^]]/ =~ "]"      # => nil

可以使用反斜线对"^", "-", "]" 以及 "\\"(反斜线)进行转义,使其匹配该字符本身。

p /[\^]/ =~ "^"   # => 0
p /[\-]/ =~ "-"   # => 0
p /[\]]/ =~ "]"   # => 0
p /[\\]/ =~ "\\"  # => 0

在[]中可以使用反斜线表示法以及正则表达式\w, \W, \s, \S, \d, \D (这些都是表示字符范围的简写法)。

请注意,下列包含否定意味的字符范围也将匹配换行符(正则表达式 \W,\D 也是如此)。

p /[^a-z]/ =~ "\n"    # => 0

字符范围中也可以使用下列特殊的表达法,但是,将来这些表达法是否会继续得到支持还未可知(所以此处从略,欲知详情请参考grep(1)的手册)。

[:alnum:]  数字和字母 0-9a-zA-Z
[:alpha:]  字母 a-zA-Z
[:blank:]  空白类
[:cntrl:]  控制字符
[:digit:]  数字
[:graph:]  除空白以外的可打印可视字符
[:lower:]  小写字母
[:print:]  可视字符
[:punct:]  符号
[:space:]  空白字符
[:upper:]  大写字母
[:xdigit:] 16进制字符

例: (包括"[]"在内,"[:...:]"表示1个字符。并非文字类的"[]")

p /[[:alnum:]][[:cntrl:]]/ =~ "a\x01"  # => 0

注: 全角字符不在考虑范围之内。即使指定让正则表达式对汉字进行匹配时,[:alpha:]等也不会匹配全角的字母。

p /[[:alpha:]]/e =~ "A"        # => nil

回缩(backtrack)

用特殊括号(?> )将正则表达式括起来后,与该正则表达式相匹配的字符串中的回缩功能就将失效。举例如下。

例如在通常的正则表达式中

p /(a*)ab/ === 'aaab'

是匹配的。该匹配过程如下所示。

  1. 正则表达式 a* 从索引0开始匹配3个a
  2. 正则表达式 a 匹配失败
  3. 正则表达式 a* 将匹配部分稍稍“缩小”一下,匹配2个a(使用了回缩功能)
  4. 正则表达式 a 与字符a匹配
  5. 正则表达式 b 与字符b匹配
但是,如果用括号(?> )把正则表达式括起来的话,就不再匹配了。详细过程如下。

  1. 正则表达式 a* 从索引0开始匹配3个a
  2. 正则表达式 a 匹配失败
  3. a* 想把匹配部分回缩一下,但由于特殊括号的作用,回缩功能失效。
  4. 正则表达式 a* 从索引1开始匹配2个a

接下来的匹配都不成功,最终导致整体匹配失败。

简单说来,通常的正则表达式是“贪婪型的匹配”,而(?> )则是“超贪婪型的匹配”,因为它一旦匹配成功就决不放手。

范例

为了便于您拷贝使用,我们将其代入到以$re_开头的全局变量中。

数值

用逗号将数字划分成3位一组的形式