# **第 16 章 正则表达式类**
Ruby 的特点是“万物皆对象”,正则表达式也不例外。正则表达式对象所属的类就是接下来我们将要介绍的 `Regexp` 类。
-
**正则表达式的写法与用法**
介绍正则表达式的基础。
-
**正则表达式的模式与匹配**
介绍什么是正则表达式的元字符(meta character)、以及如何通过元字符进行匹配。
-
**通过 quote 方法创建正则表达式**
介绍一些不常用的正则表达式的创建方法。
-
**正则表达式的选项**
介绍几个能够用正则表达式设定的选项
-
**捕获(capture)**
介绍通过正则表达式匹配时的一个重要功能——捕获。
-
**使用正则表达式的方法**
介绍以正则表达式为参数的方法。
-
**正则表达式的例子**
介绍如何通过正则表达式匹配 URL。
### **16.1 关于正则表达式**
下面我们开始介绍有关正则表达式的一些概念及用法。
### **16.1.1 正则表达式的写法与用法**
正如我们在 2.3 节中介绍的那样,正则表达式描述的是一种“模式”,该模式被用于匹配字符串。一般情况下,我们把正则表达式模式的对象(`Regexp` 类对象)称为“正则表达式对象”,或直接称为“正则表达式”。
到目前为止,我们都是使用纯文本文字作为模式,而实际上还有更复杂的模式。例如,通过模式可以很简单地匹配“首字符为 A 到 D 中的某个字母,从第 2 个字符开始为数字”这样的字符串(这个模式可写为 `/[A-D]\d+/`)。
但模式也并非是万能的,例如像“与 Ruby 类似的字符串”这种含糊的模式就无法书写。模式说明的东西应该更具体一些,例如“以 `R` 开头,以 `y` 结尾,由 4 个字母组成”(这个模式可写为 `/R..y/`)。
为了能够熟练掌握正则表达式,首先就需要理解正则表达式模式的写法。因此,在学习正则表达式的具体用法前,本章将首先介绍一下正则表达式的写法,然后再介绍如何使用正则表达式。
### **16.1.2 正则表达式对象的创建方法**
在程序中,通过用 `//` 将表示正则表达式模式的字符串括起来,就可以非常简单地创建出正则表达式。
另外,我们也可以使用类方法 `Regexp.new(str)` 来创建对象。当程序中已经定义了字符串对象 str,且希望根据这个字符串来创建正则表达式时,用这个方法会比较好。
~~~
re = Regexp.new("Ruby")
~~~
除上述两种方法外,与数组、字符串一样,我们也可以通过使用 `%` 的特殊语法来创建。正则表达式的情况下使用的是 `%r`,如果正则模式中包含 `/`,用这种方法会比较方便。语法如下所示:
**`%r` (模式)
`%r` <模式>
`%r` |模式|
`%r`! 模式!**
### **16.2 正则表达式的模式与匹配**
了解正则表达式的创建方法后,接下来讨论一下模式。`=~` 方法是正则表达式中常用的方法,可以用来判断正则表达式与指定字符串是否匹配。
**正则表达式 `=~` 字符串**
无法匹配时返回 `nil`,匹配成功则返回该字符串起始字符的位置。
正如我们在第 5 章中介绍的那样,Ruby 会将 `nil` 与 `false` 解析为“假”,将除此以外的值解析为“真”,因此,如果要根据匹配结果执行不同的处理,则可以像下面这样写。
**`if` 正则表达式 `=~` 字符串
匹配时的处理
`else`
不匹配时的处理
`end`**
我们还可以使用 `!~` 来颠倒“真”与“假”。
### **16.2.1 匹配普通字符**
我们首先来看看如何通过模式进行简单的匹配(表 16.1)。当模式中只写有英文、数字时,正则表达式会单纯地根据目标字符串中是否包含该模式中的字符来判断是否匹配(在本章的所有表中,匹配部分都用▶匹配部分◀来表示)。
**表 16.1 匹配普通字符的例子**
<table border="1" data-line-num="72 73 74 75 76 77 78 79 80" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/ABC/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/ABC/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDEF"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀DEF"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/ABC/</code></p> </td> <td> <p class="表格单元格"><code>"123ABC"</code></p> </td> <td> <p class="表格单元格"><code>"123▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/ABC/</code></p> </td> <td> <p class="表格单元格"><code>"A1B2C3"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/ABC/</code></p> </td> <td> <p class="表格单元格"><code>"AB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/AB/</code></p> </td> <td> <p class="表格单元格"><code>"abc"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr></tbody></table>
### **16.2.2 匹配行首与行尾**
在上一节的例子中,`/ABC/` 模式的情况下,只要是包含 `ABC` 的字符串就都可以匹配。但如果我们只想匹配 `ABC` 这一字符串,也就是说只匹配 `"ABC"`,而不匹配 `"012ABC"`、`"ABCDEF"` 等,这时的模式应该怎么写呢?这种情况下,我们可以使用模式 `/^ABC$/`。
`^`、`$` 是有特殊意义的字符。但它们并不用于匹配 `^` 与 `$` 字符。像这样的特殊字符,我们称之为元字符(meta character)。在稍后的章节中,我们会介绍 `^`、`$` 以外的其他元字符。
`^` 表示匹配行首,`$` 表示匹配行尾(表 16.2)。也就是说,模式 `/^ABC/` 匹配行首为 `ABC` 的字符串,模式 `/ABC$/` 匹配行尾为 `ABC` 的字符串。
**表 16.2 ^ 与 & 的使用例子**
<table border="1" data-line-num="91 92 93 94 95 96 97 98 99 100 101 102" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/^ABC$/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^ABC$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDEF"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^ABC$/</code></p> </td> <td> <p class="表格单元格"><code>"123ABC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^ABC/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^ABC/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDEF"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀DEF"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^ABC/</code></p> </td> <td> <p class="表格单元格"><code>"123ABC"</code></p> </td> <td> <p class="表格单元格">不匹配</p> </td> </tr><tr><td> <p class="表格单元格"><code>/ABC$/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/ABC$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDEF"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/ABC$/</code></p> </td> <td> <p class="表格单元格"><code>"123ABC"</code></p> </td> <td> <p class="表格单元格"><code>"123▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr></tbody></table>
可能有人会觉得行首、行尾不是字符,“匹配行首”这样的说法比较别扭,不过用多了就会习惯了。
> **专栏**
> **行首与行尾**
> `^`、`$` 分别匹配“行首”、“行尾”,而不是“字符串的开头”、“字符串末尾”。匹配字符串的开头用元字符 `\A`,匹配字符串的末尾用元字符 `\z`。
> 这两种情况有什么不同呢? Ruby 的字符串,也就是 `String` 对象中,所谓的“行”就是用换行符 `(\n)` 间隔的字符串。因此模式 `/^ABC/` 也可以匹配字符串 `"012\nABC"`。也就是说,
~~~
012
ABC
~~~
> 像上面这种跨两行的字符串的情况下,由于第 2 行是以 `ABC` 开头的,因此也可以匹配。
> 那么为什么要将行首 `/` 行尾与字符串的开头 `/` 结尾分开定义呢?这是有历史原因的。
> 具体来说,原本正则表达式只能逐行匹配字符串,不能匹配多行字符串。因此就可以认为一个“字符串”就是一“行”。
> 但是,随着正则表达式的广泛使用,人们开始希望可以匹配多行字符串。而如果仍用 `^`、`$` 来匹配字符串的开头、结尾的话就很容易造成混乱,因此就另外定义了匹配字符串开头、结尾的元字符。
> 另外,还有一个与 `\z` 类似的表现,就是 `\Z`,不过两者的作用有点不一样。`\Z` 虽然也是匹配字符串末尾的元字符,但它有一个特点,就是如果字符串末尾是换行符,则匹配换行符前一个字符。
~~~
p "abc\n".gsub(/\z/, "!") => "abc\n!"
p "abc\n".gsub(/\Z/, "!") => "abc!\n!"
~~~
> 我们一般常用 `\z`,而很少使用 `\Z`。
### **16.2.3 指定匹配字符的范围**
有时候我们会希望匹配“`ABC` 中的 1 个字符”。像这样,选择多个字符中的 1 个时,我们可以使用 []。
-
**[`AB`] ……A或B**
-
**[`ABC`] ……A或B或C**
-
**[`CBA`] ……同上(与[]中的顺序无关)**
-
**[`012ABC`] ……0、1、2、A、B、C中的1个字符**
不过,如果按照这样的写法,那么匹配“从 A 到 Z 的全部英文字母”时就麻烦了。这种情况下,我们可以在 `[]` 中使用 `-`,来表示一定范围内的字符串。
-
**[`A-Z`] ……从A到Z的全部大写英 文字母**
-
**[`a-z`] ……从a 到z 的全部小写英文字母**
-
**[`0-9`] ……从0到9 的全部数字**
-
**[`A-Za-z`] ……从A到Z与从a到z的全部英文字母**
-
**[`A-Za-z_`] ……全部英 文字母与 _**
> **备注** 字符的范围也称为“字符类”。请注意这里的“类”与面向对象中的“类”的意义是不一样的。
如果 `-` 是 `[]` 中首个或者最后 1 个字符,那么就只是单纯地表示 `-` 字符。反过来说,如果 `-` 表示的不是字符类,而是单纯的字符 `-`,那么就必须写在模式的开头或者末尾。
- **[`A-Za-z0-9_-`] ……全部英文字母、全部数字、_、-**
在 `[]` 的开头使用 `^` 时,`^` 表示指定字符以外的字符。
-
**[`^ABC`] ……A、B、C 以外的字符**
-
**[`^a-zA-Z`] ……a 到 z,A 到 Z(英文字母)以外的字符**
表 16.3 为一些实际进行匹配的例子。另外,在 1 个模式中还可以使用多个 `[]`(表 16.4)。
**表 16.3 使用 [] 的例子**
<table border="1" data-line-num="167 168 169 170 171 172 173 174 175 176 177 178" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/[ABC]/</code></p> </td> <td> <p class="表格单元格"><code>"B"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">B</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC]/</code></p> </td> <td> <p class="表格单元格"><code>"BCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">B</span>◀CD"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC]/</code></p> </td> <td> <p class="表格单元格"><code>"123"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/a[ABC]c/</code></p> </td> <td> <p class="表格单元格"><code>"aBc"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">aBc</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/a[ABC]c/</code></p> </td> <td> <p class="表格单元格"><code>"1aBcDe"</code></p> </td> <td> <p class="表格单元格"><code>"1▶<span style="background-color:#EEEFEF">aBc</span>◀De"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/a[ABC]c/</code></p> </td> <td> <p class="表格单元格"><code>"abc"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/[^ABC]/</code></p> </td> <td> <p class="表格单元格"><code>"1"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">1</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[^ABC]/</code></p> </td> <td> <p class="表格单元格"><code>"A"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/a[^ABC]c/</code></p> </td> <td> <p class="表格单元格"><code>"aBcabc"</code></p> </td> <td> <p class="表格单元格"><code>"aBc▶<span style="background-color:#EEEFEF">abc</span>◀"</code></p> </td> </tr></tbody></table>
**表 16.4 使用多个 [] 的例子**
<table border="1" data-line-num="181 182 183 184 185 186 187 188 189 190 191 192 193 194 195" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"AB"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AB</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"AA"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AA</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"CA"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">CA</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"CCCCA"</code></p> </td> <td> <p class="表格单元格"><code>"CCC▶<span style="background-color:#EEEFEF">CA</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"xCBx"</code></p> </td> <td> <p class="表格单元格"><code>"x▶<span style="background-color:#EEEFEF">CB</span>◀x"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"CC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"CxAx"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/[ABC][AB]/</code></p> </td> <td> <p class="表格单元格"><code>"C"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/[0-9][A-Z]/</code></p> </td> <td> <p class="表格单元格"><code>"0A"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">0A</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[0-9][A-Z]/</code></p> </td> <td> <p class="表格单元格"><code>"000AAA"</code></p> </td> <td> <p class="表格单元格"><code>"00▶<span style="background-color:#EEEFEF">0A</span>◀AA"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[^A-Z][A-Z]/</code></p> </td> <td> <p class="表格单元格"><code>"1A2B3C"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">1A</span>◀2B3C"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/[^0-9][^A-Z]/</code></p> </td> <td> <p class="表格单元格"><code>"1A2B3C"</code></p> </td> <td> <p class="表格单元格"><code>"1▶<span style="background-color:#EEEFEF">A2</span>◀B3C"</code></p> </td> </tr></tbody></table>
### **16.2.4 匹配任意字符**
有时候我们会希望定义这样的模式,即“不管是什么字符,只要匹配 1 个字符就行”。这种情况下,我们可以使用元字符 `.`。
-
**. ……匹配任意字符**
**表 16.5 为实际进行匹配的例子。**
| 模式 | 字符串 | 匹配部分 |
|-----|-----|-----|
| `/A.C/` | `"ABC"` | `"▶ABC◀"` |
| `/A.C/` | `"AxC"` | `"▶AxC◀"` |
| `/A.C/` | `"012A3C456"` | `"012▶A3C◀456"` |
| `/A.C/` | `"AC"` | (不匹配) |
| `/A.C/` | `"ABBC"` | (不匹配) |
| `/A.C/` | `"abc"` | (不匹配) |
| `/aaa.../` | `"00aaabcde"` | `"00▶aaabcd◀e"` |
| `/aaa.../` | `"aaabb"` | (不匹配) |
然而,可能有读者会问:“程序在什么时候会需要能够匹配任意字符的字符呢”。的确,任意字符都能匹配的话,也就没有必要特意指定了。
在下面两种情况下,一般会使用这个元字符。
-
**在希望指定字符数时使用**
`/^...$/` 这样的模式可以匹配字符数为 3 的行。
-
**与稍后介绍的元字符 * 配合使用**
关于这部分的详细内容请参考 16.2.6 节。
### **16.2.5 使用反斜杠的模式**
与字符串一样,我们也可以使用 \+1 个英文字母这样的形式来表示换行、空白等特殊字符。
-
**`\s`**
表示空白符,匹配空格(`0x20`)、制表符(Tab)、换行符、换页符(表 16.6)。
**表 16.6 使用 \s 的例子**
| 模式 | 字符串 | 匹配部分 |
|-----|-----|-----|
| `/ABC\sDEF/` | `"ABC DEF"` | `"▶ABC DEF◀"` |
| `/ABC\sDEF/` | `"ABC\tDEF"` | `"▶ABC\tDEF◀"` |
| `/ABC\sDEF/` | `"ABCDEF"` | (不匹配) |
-
**`\d`**
匹配 0 到 9 的数字(表 16.7)。
**表 16.7 使用 \d 的例子**
| 模式 | 字符串 | 匹配部分 |
|-----|-----|-----|
| `/\d\d\d-\d\d\d\d/` | `"012-3456"` | `"▶012-3456◀"` |
| `/\d\d\d-\d\d\d\d/` | `"01234-012345"` | `"01▶234-0123◀45"` |
| `/\d\d\d-\d\d\d\d/` | `"ABC-DEFG"` | (不匹配) |
| `/\d\d\d-\d\d\d\d/` | `"012-21"` | (不匹配) |
-
**`\w`**
匹配英文字母与数字(表 16.8)。
**表 16.8 使用 \w 的例子**
| 模式 | 字符串 | 匹配部分 |
|-----|-----|-----|
| `/\w\w\w/` | `"ABC"` | `"▶ABC◀"` |
| `/\w\w\w/` | `"abc"` | `"▶abc◀"` |
| `/\w\w\w/` | `"012"` | `"▶012◀"` |
| `/\w\w\w/` | `"AB C"` | (不匹配) |
| `/\w\w\w/` | `"AB\nC"` | (不匹配) |
-
**`\A`**
匹配字符串的开头(表 16.9)。
**表 16.9 使用 \A 的例子**
| 模式 | 字符串 | 匹配部分 |
|-----|-----|-----|
| `/\AABC/` | `"ABC"` | `"▶ABC◀"` |
| `/\AABC/` | `"ABCDEF"` | `"▶ABC◀DEF"` |
| `/\AABC/` | `"012ABC"` | (不匹配) |
| `/\AABC/` | `"012\nABC"` | (不匹配) |
-
**`\z`**
匹配字符串的末尾(表 16.10)。
**表 16.10 使用 \z 的例子**
| 模式 | 字符串 | 匹配部分 |
|-----|-----|-----|
| `/ABC\z/` | `"ABC"` | `"▶ABC◀"` |
| `/ABC\z/` | `"012ABC"` | `"012▶ABC◀"` |
| `/ABC\z/` | `"ABCDEF"` | (不匹配) |
| `/ABC\z/` | `"012\nABC"` | `"012\n▶ABC◀"` |
| `/ABC\z/` | `"ABC\nDEF"` | (不匹配) |
-
**元字符转义**
我们还可以用 `\` 对元字符进行转义。在 `\` 后添加 `^`、`$`、`[` 等非字母数字的元字符后,该元字符就不再发挥元字符的功能,而是直接被作为元字符本身来匹配(表 16.11)。
**表 16.11 使用 \ 的例子**
| 模式 | 字符串 | 匹配部分 |
|-----|-----|-----|
| `/ABC\[/` | `"ABC["` | `"▶ ABC[ ◀"` |
| `/\^ABC/` | `"ABC"` | (不匹配) |
| `/\^ABC/` | `"012^ABC"` | `"012▶^ABC◀"` |
### **16.2.6 重复**
有时候,我们会需要重复匹配多次相同的字符。例如,匹配“`"Subject:"` 字符串后多个空白符,空白符后又有字符串这样的行”(这是匹配电子邮件的主题时使用的模式)。
正则表达式中用以下元字符来表示重复匹配的模式。
-
*** ……重复 0 次以上**
-
**+ ……重复 1 次以上**
-
**? ……重复 0 次或 1 次**
首先我们来看看表示匹配重复 0 次以上的 *(表 16.12)。
**表 16.12 使用 * 的例子**
<table border="1" data-line-num="324 325 326 327 328 329 330 331 332 333 334 335 336 337 338" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/A*/</code></p> </td> <td> <p class="表格单元格"><code>"A"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">A</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A*/</code></p> </td> <td> <p class="表格单元格"><code>"AAAAAA"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AAAAAA</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A*/</code></p> </td> <td> <p class="表格单元格"><code>""</code></p> </td> <td> <p class="表格单元格"><code>"▶◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A*/</code></p> </td> <td> <p class="表格单元格"><code>"BBB"</code></p> </td> <td> <p class="表格单元格"><code>"▶◀"BBB</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A*C/</code></p> </td> <td> <p class="表格单元格"><code>"AAAC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AAAC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A*C/</code></p> </td> <td> <p class="表格单元格"><code>"BC"</code></p> </td> <td> <p class="表格单元格"><code>"B▶<span style="background-color:#EEEFEF">C</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A*C/</code></p> </td> <td> <p class="表格单元格"><code>"AAAB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/AAA*C/</code></p> </td> <td> <p class="表格单元格"><code>"AAC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AAC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/AAA*C/</code></p> </td> <td> <p class="表格单元格"><code>"AC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.*C/</code></p> </td> <td> <p class="表格单元格"><code>"AB012C"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AB012C</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.*C/</code></p> </td> <td> <p class="表格单元格"><code>"AB CD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AB C</span>◀D"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.*C/</code></p> </td> <td> <p class="表格单元格"><code>"ACDE"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AC</span>◀DE"</code></p> </td> </tr></tbody></table>
使用 `*` 匹配电子邮件的主题的模式如表 16.13 所示。
**表 16.13 使用 * 的例子(之二)**
<table border="1" data-line-num="343 344 345 346 347 348 349" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/^Subject:\s*.*$/</code></p> </td> <td> <p class="表格单元格"><code>"Subject: foo"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">Subject: foo</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^Subject:\s*.*$/</code></p> </td> <td> <p class="表格单元格"><code>"Subject: Re: foo"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">Subject: Re: foo</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^Subject:\s*.*$/</code></p> </td> <td> <p class="表格单元格"><code>"Subject:Re^2 foo"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">Subject:Re^2 foo</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^Subject:\s*.*$/</code></p> </td> <td> <p class="表格单元格"><code>"in Subject:Re foo"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr></tbody></table>
`+` 表示匹配重复 1 次以上(表 16.14)。
**表 16.14 使用 + 的例子**
<table border="1" data-line-num="354 355 356 357 358 359 360 361 362 363 364 365 366 367 368" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/A+/</code></p> </td> <td> <p class="表格单元格"><code>"A"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">A</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A+/</code></p> </td> <td> <p class="表格单元格"><code>"AAAAAA"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AAAAAA</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A+/</code></p> </td> <td> <p class="表格单元格"><code>""</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/A+/</code></p> </td> <td> <p class="表格单元格"><code>"BBB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/A+C/</code></p> </td> <td> <p class="表格单元格"><code>"AAAC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AAAC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A+C/</code></p> </td> <td> <p class="表格单元格"><code>"BC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/A+C/</code></p> </td> <td> <p class="表格单元格"><code>"AAAB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/AAA+C/</code></p> </td> <td> <p class="表格单元格"><code>"AAC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/AAA+C/</code></p> </td> <td> <p class="表格单元格"><code>"AC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.+C/</code></p> </td> <td> <p class="表格单元格"><code>"AB012C"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AB012C</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.+C/</code></p> </td> <td> <p class="表格单元格"><code>"AB CD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AB C</span>◀D"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.+C/</code></p> </td> <td> <p class="表格单元格"><code>"ACDE"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr></tbody></table>
`?` 表示匹配重复 0 次或 1 次(表 16.15)。
**表 16.15 使用 ? 的例子**
<table border="1" data-line-num="373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/^A?$/</code></p> </td> <td> <p class="表格单元格"><code>"A"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">A</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^A?$/</code></p> </td> <td> <p class="表格单元格"><code>""</code></p> </td> <td> <p class="表格单元格"><code>"▶◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^A?$/</code></p> </td> <td> <p class="表格单元格"><code>"AAAAAA"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^A?C/</code></p> </td> <td> <p class="表格单元格"><code>"AC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^A?C/</code></p> </td> <td> <p class="表格单元格"><code>"AAAC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^A?C/</code></p> </td> <td> <p class="表格单元格"><code>"BC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^A?C/</code></p> </td> <td> <p class="表格单元格"><code>"C"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">C</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/AAA?C/</code></p> </td> <td> <p class="表格单元格"><code>"AAAC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AAAC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/AAA?C/</code></p> </td> <td> <p class="表格单元格"><code>"AAC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AAC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/AAA?C/</code></p> </td> <td> <p class="表格单元格"><code>"AC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.?C/</code></p> </td> <td> <p class="表格单元格"><code>"ACDE"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AC</span>◀DE"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.?C/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDE"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀DE"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.?C/</code></p> </td> <td> <p class="表格单元格"><code>"AB012C"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.?C/</code></p> </td> <td> <p class="表格单元格"><code>"AB CD"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr></tbody></table>
### **16.2.7 最短匹配**
匹配 0 次以上的 `*` 以及匹配 1 次以上的 `+` 会匹配尽可能多的字符 1。相反,匹配尽可能少的字符 2 时(重复后的模式首次出现的位置之前的部分),我们可以用以下元字符:
1也称贪婪匹配。——译者注
2也称懒惰匹配。——译者注
-
***? ……0 次以上的重复中最短的部分**
-
**+? ……1 次以上的重复中最短的部分**
下表(16.16)是与不带 `?` 时的 `*` 与 `+` 的对比。
**表 16.16 使用 *、+、*?、+? 的例子**
<table border="1" data-line-num="401 402 403 404 405 406 407 408 409 410 411" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/A.*B/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCDABCDAB</span>◀CD"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.*C/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCDABCDABC</span>◀D"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.*?B/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">AB</span>◀CDABCDABCD"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.*?C/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀DABCDABCD"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.+B/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCDABCDAB</span>◀CD"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.+C/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCDABCDABC</span>◀D"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.+?B/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCDAB</span>◀CDABCD"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/A.+?C/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDABCDABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀DABCDABCD"</code></p> </td> </tr></tbody></table>
### **16.2.8 () 与重复**
在刚才的例子中,我们只是重复匹配了 1 个字符,而通过使用 `()`,我们还可以重复匹配多个字符(表 16.17)。
**表 16.17 使用 () 的例子**
<table border="1" data-line-num="418 419 420 421 422 423 424 425 426 427 428 429 430 431 432" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/^(ABC)*$/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)*$/</code></p> </td> <td> <p class="表格单元格"><code>""</code></p> </td> <td> <p class="表格单元格"><code>"▶◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)*$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)*$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABCAB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)+$/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)+$/</code></p> </td> <td> <p class="表格单元格"><code>""</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)+$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)+$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABCAB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)?$/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)?$/</code></p> </td> <td> <p class="表格单元格"><code>""</code></p> </td> <td> <p class="表格单元格"><code>"▶◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)?$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC)?$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABCAB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr></tbody></table>
### **16.2.9 选择**
我们可以用 `|` 在几个候补模式中匹配任意一个(表 16.18)。
**表 16.18 使用 | 的例子**
<table border="1" data-line-num="439 440 441 442 443 444 445 446 447 448 449" width="90%"><thead><tr><th> <p class="表头单元格">模式</p> </th> <th> <p class="表头单元格">字符串</p> </th> <th> <p class="表头单元格">匹配部分</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>/^(ABC|DEF)$/</code></p> </td> <td> <p class="表格单元格"><code>"ABC"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABC</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC|DEF)$/</code></p> </td> <td> <p class="表格单元格"><code>"DEF"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">DEF</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC|DEF)$/</code></p> </td> <td> <p class="表格单元格"><code>"AB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(ABC|DEF)$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCDEF"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(AB|CD)+$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCD"</code></p> </td> <td> <p class="表格单元格"><code>"▶<span style="background-color:#EEEFEF">ABCD</span>◀"</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(AB|CD)+$/</code></p> </td> <td> <p class="表格单元格"><code>""</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(AB|CD)+$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABC"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr><tr><td> <p class="表格单元格"><code>/^(AB|CD)+$/</code></p> </td> <td> <p class="表格单元格"><code>"ABCABCAB"</code></p> </td> <td> <p class="表格单元格">(不匹配)</p> </td> </tr></tbody></table>
### **16.3 使用 quote 方法的正则表达式**
有时候我们可能会希望转义(escape)正则表达式中的所有元字符。而 `quote` 方法就可以帮我们实现这个想法。`quote` 方法会返回转义了元字符后的正则表达式字符串,然后再结合 `new` 方法,就可以生成新的正则表达式对象了。
~~~
re1 = Regexp.new("abc*def")
re2 = Regexp.new(Regexp.quote("abc*def"))
p (re1 =~ "abc*def") #=> nil
p (re2 =~ "abc*def") #=> 0
~~~
`quote` 方法的问题在于不能以元字符的格式写元字符。因此,在写一些复杂的正则表达式时,建议不要使用 `quote` 方法,而是乖乖地对元字符进行转义。
### **16.4 正则表达式的选项**
正则表达式中还有选项,使用选项可以改变正则表达式的一些默认效果。
设定正则表达式的选项时,只需在 `/…/` 的后面指定即可,如 `/… /im`,这里的 `i` 以及 `m` 就是正则表达式的选项。
-
**`i`**
忽略英文字母大小写的选项。指定这个选项后,无论字符串中的字母是大写还是小写都会被匹配。
-
**`x`**
忽略正则表达式中的空白字符以及 `#` 后面的字符的选项。指定这个选项后,我们就可以使用 `#` 在正则表达式中写注释了。
-
**`m`**
指定这个选项后,就可以使用 `.` 匹配换行符了。
~~~
str = "ABC\nDEF\nGHI"
p /DEF.GHI/ =~ str #=> nil
p /DEF.GHI/m =~ str #=> 4
~~~
表 16.19 中总结了几种常用的选项。
**表 16.19 正则表达式的选项**
<table border="1" data-line-num="485 486 487 488 489 490 491" width="90%"><thead><tr><th> <p class="表头单元格">选项</p> </th> <th> <p class="表头单元格">选项常量</p> </th> <th> <p class="表头单元格">意义</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>i</code></p> </td> <td> <p class="表格单元格"><code>Regexp::IGNORECASE</code></p> </td> <td> <p class="表格单元格">不区分大小写</p> </td> </tr><tr><td> <p class="表格单元格"><code>x</code></p> </td> <td> <p class="表格单元格"><code>Regexp::EXTENDED</code></p> </td> <td> <p class="表格单元格">忽略模式中的空白字符</p> </td> </tr><tr><td> <p class="表格单元格"><code>m</code></p> </td> <td> <p class="表格单元格"><code>R egexp::MULTILINE</code></p> </td> <td> <p class="表格单元格">匹配多行</p> </td> </tr><tr><td> <p class="表格单元格"><code>o</code></p> </td> <td> <p class="表格单元格">(无)</p> </td> <td> <p class="表格单元格">只使用一次内嵌表达式</p> </td> </tr></tbody></table>
`Regexp.new` 方法中的第 2 个参数可用于指定选项常量。只需要 1 个参数时,可不指定第 2 个参数或者直接指定 `nil`。
例如,`/Ruby` 脚本 `/i` 这一正则表达式,可以像下面那样写:
~~~
Regexp.new("Ruby 脚本", Regexp::IGNORECASE)
~~~
另外,我们还可以用 `|` 指定多个选项。这时,`/Ruby` 脚本 `/im` 这一正则表达式就变成了下面这样:
~~~
Regexp.new("Ruby 脚本",
Regexp::IGNORECASE | Regexp::MULTILINE)
~~~
### **16.5 捕获**
除了检查字符是否匹配外,正则表达式还有另外一个常用功能,甚至可以说是比匹配更加重要的功能——捕获(后向引用)。
所谓捕获,就是从正则表达式的匹配部分中提取其中的某部分。通过“`$` 数字”这种形式的变量,就可以获取匹配了正则表达式中的用 `()` 括住的部分的字符串。
~~~
/(.)(.)(.)/ =~ "abc"
first = $1
second = $2
third = $3
p first #=> "a"
p second #=> "b"
p third #=> "c"
~~~
在进行匹配的时候,我们只知道是否匹配、匹配第几个字符之类的信息。而使用捕获后,我们就可以知道哪部分被匹配了。因此,通过这个功能,我们就可以非常方便地对字符串进行分析。
在 16.2.8 节中我们提到了 `()` 也被用于将多个模式整理为一个。在修改程序中的正则表达式时,如果改变了 `()` 的数量,那么将要引用的部分的索引也会随之改变,有时就会带来不便。这种情况下,我们可以使用 `(?: )` 过滤不需要捕获的模式。
~~~
/(.)(\d\d)+(.)/ =~ "123456"
p $1 #=> "1"
p $2 #=> "45"
p $3 #=> "6"
/(.)(?:\d\d)+(.)/ =~ "123456"
p $1 #=> "1"
p $2 #=> "6"
~~~
除了“`$` 数字”这种形式以外,保存匹配结果的变量还有 `$`、`$&`、`$`,分表代表匹配部分前的字符串、匹配部分的字符串、匹配部分后的字符串。为了方便大家快速理解这 3 个变量的含义,我们来看看下面这个例子:
~~~
/C./ =~ "ABCDEF"
p $` #=> "AB"
p $& #=> "CD"
p $' #=> "EF"
~~~
这样一来,我们就可以将字符串整体分为匹配部分与非匹配部分,并将其分别保存在 3 个不同的变量中。
### **16.6 使用正则表达式的方法**
字符串相关的方法中有一些使用了正则表达式,接下来我们就来介绍一下其中的 `sub` 方法、`gsub` 方法、`scan` 方法。
### **16.6.1 sub 方法与 gsub 方法**
`sub` 方法与 `gsub` 方法的作用是用指定的字符置换字符串中的某部分字符。
`sub` 方法与 `gsub` 方法都有两个参数。第 1 个参数用于指定希望匹配的正则表达式的模式,第 2 个参数用于指定与匹配部分置换的字符。`sub` 方法只置换首次匹配的部分,而 `gsub` 方法则会置换所有匹配的部分。
~~~
str = "abc def g hi"
p str.sub(/\s+/,' ') #=> "abc def g hi"
p str.gsub(/\s+/,' ') #=> "abc def g hi"
~~~
`/\s+/` 是用于匹配 1 个以上的空白字符的模式。因此在本例中,`sub` 方法与 `gsub` 方法会将匹配的空白部分置换为 1 个空白。`sub` 方法只会置换 `abc` 与 `def` 间的空白,而 `gsub` 方法则会将字符串后面匹配的空白部分全部置换。
`sub` 方法与 `gsub` 方法还可以使用块。这时,程序会将字符串中匹配的部分传递给块,并在块中使用该字符串进行处理。这样一来,块中返回的字符串就会置换字符串中匹配的部分。
~~~
str = "abracatabra"
nstr = str.sub(/.a/) do |matched|
'<'+matched.upcase+'>'
end
p nstr #=> "ab<RA>catabra"
nstr = str.gsub(/.a/) do |matched|
'<'+matched.upcase+'>'
end
p nstr #=> "ab<RA><CA><TA>b<RA>"
~~~
在本例中,程序会将字符串 `a` 以及 `a` 之前的字母转换为大写,并用 `<>` 将其括起来。
`sub` 方法与 `gsub` 方法也有带 `!` 的方法。`sub!` 方法与 `gusb!` 方法会直接将作为接受者的对象变换为置换后的字符串。
### **16.6.2 scan 方法**
`scan` 方法能像 `gsub` 方法那样获取匹配部分的字符,但不能做置换操作。因此,当需要对匹配部分做某种处理时,可以使用该方法(代码清单 16.1)。
**代码清单 16.1 scan1.rb**
~~~
"abracatabra".scan(/.a/) do |matched|
p matched
end
~~~
> **执行示例**
~~~
> ruby scan1.rb
"ra"
"ca"
"ta"
"ra"
~~~
在正则表达式中使用 `()` 时,匹配部分会以数组的形式返回(代码清单 16.2)。
**代码清单 16.2 scan2.rb**
~~~
"abracatabra".scan(/(.)(a)/) do |matched|
p matched
end
~~~
> **执行示例**
~~~
> ruby scan2.rb
["r", "a"]
["c", "a"]
["t", "a"]
["r", "a"]
~~~
另外,如果指定与 `()` 相等数量的块参数,则返回的结果就不是数组,而是各个元素(代码清单 16.3)。
**代码清单 16.3 scan3.rb**
~~~
"abracatabra".scan(/(.)(a)/) do |a, b|
p a+"-"+b
end
~~~
> **执行示例**
~~~
> ruby scan3.rb
"r-a"
"c-a"
"t-a"
"r-a"
~~~
如果没有指定块,则直接返回匹配的字符串数组。
~~~
p "abracatabra".scan(/.a/) #=> ["ra", "ca", "ta", "ra"]
~~~
### **16.7 正则表达式的例子**
接下来我们来看看用正则表达式匹配 URL 的例子。
首先我们需要“找出包含 URL 的行”。创建表示完整的 URL 的正则表达式会非常复杂,不过我们可以稍微变通一下,把目标改为“找出类似于 URL 的字符串”,这时,就可以用如下模式来进行匹配。
~~~
/http:\/\//
~~~
这个匹配模式的好处在于便于操作,而且也的确可以匹配 URL。
在此基础上,我们还可以进一步写出“获取类似于 URL 的字符串中的某部分”的正则表达式。例如,获取 HTTP 的 URL 中的服务器地址的模式时,可以像下面这样书写。
~~~
/http:\/\/([^\/]*)\//
~~~
`[^\/]*` 表示匹配不含 `/` 的连续字符串。
上述例子中使用了较多 `/`,不便于阅读,这种情况下我们可以使用 `%r` 将其改写成像下面那样:
~~~
%r|http://([^/]*)/|
~~~
现在我们就来看看这样写是否可以匹配(代码清单 16.4)。
**代码清单 16.4 url_match.rb**
~~~
str = "http://www.ruby-lang.org/ja/"
%r|http://([^/]*)/| =~ str
print "server address: ", $1, "\n"
~~~
> **执行示例**
~~~
> ruby url_match.rb
server address: www.ruby-lang.org
~~~
可以发现,的确可以获取服务器地址。
然后,我们再看看获取服务器地址以外部分的正则表达式。
~~~
%r|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|
~~~
这是在 RFC2396“Uniform Resource Identifiers(URI)”这个定义 URI 语法的文件中使用的正则表达式。
这个正则表达式可以被原封不动地用在 Ruby 中。如果用这个正则表达式进行匹配,则 HTTP 等协议名会被保存在 `$2` 中,服务器地址等会被保存在 `$4` 中,路径名会被保存在 `$5` 中,请求部分会被保存在 `$7` 中,片段(fragment)会被保存在 `$9` 中。
例如,http://www.example.co.jp/foo/?name=bar#bar 这个 URI 的情况下,http 为通信协议名,www.example.co.jp 为服务器地址,/foo/ 为路径名,name=bar 为请求名,baz 为片段。
![{%}](https://box.kancloud.cn/2015-10-26_562e01fd83001.png)
然而,写到这种程度,正则表达式已经变得非常复杂了。如果把正则表达式写成在任何情况下都能匹配的万能模式,就会使得正则表达式变得难以读懂,增加程序的维护成本。相比之下,只满足当前需求的正确易懂的正则表达式则往往更有效率。
例如,匹配邮政编号的正则表达式,可以写成下面这样:
~~~
/\d\d\d-\d\d\d\d/
~~~
这样,就不会匹配只有 3 位数字,或者没有 `-` 的邮政编码了。
在不需要太过严格的输入检查时,直接用 `/\d+-?\d*/` 匹配就可以了。
> **备注** 想进一步了解正则表达式的读者可以参考 Jeffrey E.F.Friedl 著的 *Mastering Regular Expressions*。该书系统地介绍了正则表达式,而且评价也非常高。
### **练习题**
1. 电子邮件的地址格式为账号名 @ 域名。请写出一个正则表达式,将账号名保存在 `$1` 中,域名保存在 `$2` 中。
2. 利用 gsub 方法,将字符串“正则表达式真难啊,怎么这么难懂!”置换为“正则表达式真简单啊,怎么这么易懂!”
3. 定义方法 `word_capitalize`,当被指定的参数为用连字符(hyphen)连接的英文字母字符串时,对被连字符分割的部分做 Capitalize 化处理(即单词的首字母大写,其余小写)。
~~~
p word_capitalize("in-reply-to") #=> "In-Reply-To"
p wrod_capitalize("X-MAILER") #=> "X-Mailer"
~~~
> 参考答案:请到图灵社区本书的“随书下载”处下载([http://www.ituring.com.cn/book/1237](http://www.ituring.com.cn/book/1237))。
- 推荐序
- 译者序
- 前言
- 本书的读者对象
- 第 1 部分 Ruby 初体验
- 第 1 章 Ruby 初探
- 第 2 章 便利的对象
- 第 3 章 创建命令
- 第 2 部分 Ruby 的基础
- 第 4 章 对象、变量和常量
- 第 5 章 条件判断
- 第 6 章 循环
- 第 7 章 方法
- 第 8 章 类和模块
- 第 9 章 运算符
- 第 10 章 错误处理与异常
- 第 11 章 块
- 第 3 部分 Ruby 的类
- 第 12 章 数值类
- 第 13 章 数组类
- 第 14 章 字符串类
- 第 15 章 散列类
- 第 16 章 正则表达式类
- 第 17 章 IO 类
- 第 18 章 File 类与 Dir 类
- 第 19 章 Encoding 类
- 第 20 章 Time 类与 Date 类
- 第 21 章 Proc 类
- 第 4 部分 动手制作工具
- 第 22 章 文本处理
- 第 23 章 检索邮政编码
- 附录
- 附录 A Ruby 运行环境的构建
- 附录 B Ruby 参考集
- 后记
- 谢辞