# **第 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 為片段。

然而,寫到這種程度,正則表達式已經變得非常復雜了。如果把正則表達式寫成在任何情況下都能匹配的萬能模式,就會使得正則表達式變得難以讀懂,增加程序的維護成本。相比之下,只滿足當前需求的正確易懂的正則表達式則往往更有效率。
例如,匹配郵政編號的正則表達式,可以寫成下面這樣:
~~~
/\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 參考集
- 后記
- 謝辭