Ruby 使用手冊

正規表示式 Regular expressions

來編寫更有趣的程式吧。這一次我們使用一個簡潔精準的樣式 (pattern)來測試一個字串是否符合描述。

這些樣式中,有些字元及字元組合具有特殊意思,包括:

[] 指定的範圍(例如:[a-z] 表示一個在 az 的範圍內的字母)
\w 一般字元 (word character),即 [0-9A-Za-z_]
\W 非一般字元 (non-word character)
\s 空白字元 (space character),即 [ \t\n\r\f]
\S 非空白字元 (non-space character)
\d 數字 (digit character),即 [0-9]
\D 非數字 (non-digit character)
\b 退位 (0x08)(僅用於指定的範圍)
\b 單字邊界(若不是於指定的範圍)
\B 非單字邊界
* 前一符號的內容出現 0 或數次。
+ 前一符號的內容出現 1 或數次。
{m,n} 前一符號的內容,最少出現 m 次,最多出現 n 次。
? 前一符號的內容最多出現一次,同 {0,1}
| 符合前一個或後一個表示式
() 分組

使用這種奇特詞彙的樣式,一般稱為正規表示式 (regular expressions)。Ruby 和 Perl 一樣,都是用斜線 (/) 包住樣式內容,而不用雙引號。如果你從未接觸過正規表示式,那麼正規表示式對你來說可能一點也不正規,但是花點時間熟悉一下絕對是明智之舉。正規表示式具備有高效率的解釋能力 (expressive power),對於樣式比對、搜尋、或操作文字字串,都能為你省卻許多麻煩(及不少程式碼)。

例如,我們想要測試一個字串是否符合一項描述:「以小寫的 f 開頭,接著是一個大寫的字母,之後隨意打些字母,只要沒有小寫的就行。」如果你熟悉 C 程式,現在腦海大概已經浮現十幾行程式碼了吧?沒錯吧,你這是自找麻煩。而用 Ruby 的話,你只要用 /^f[A-Z][^a-z]*$/ 這個正規表示式測試你的字串就可以。

那如果是測試「在角括號內含有一個十六進位數」呢?也沒問題。

ruby> def chab(s)   # "contains hex in angle brackets"
    |    (s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
    | end
  nil
ruby> chab "Not this one."
  false
ruby> chab "Maybe this?{0x35}"    # wrong kind of brackets
  false
ruby> chab "Or this?<0x38z7e>"    # bogus hex digit
  false
ruby> chab "Okay, this: <0xfc0004>."
  true

雖然正規表示式一眼看來會讓人感到迷惘,但你很快就會感到愉悅,因為它可以讓你省卻不少麻煩。

以下這個小程式可以幫你測試常規表示式(編註:你也可以使用 Rubular 這個網路服務做練習)。將它儲存為 regx.rb,並在命令列輸入 "ruby regx.rb" 以執行程式。

# 需要 ANSI 終端機!

st = "\033[7m"
en = "\033[m"

puts "Enter an empty string at any time to exit."

while true
  print "str> "; STDOUT.flush; str = gets.chop
  break if str.empty?
  print "pat> "; STDOUT.flush; pat = gets.chop
  break if pat.empty?
  re = Regexp.new(pat)
  puts str.gsub(re,"#{st}\\&#{en}")
end

這個程式需要輸入兩次,一次是字串,另一次是正規表示式。字串就會與正規表示式開始比對,然後將所有匹配的部分反白顯示。先別在意細節,很快就會來分析這段程式碼。

str> foobar
pat> ^fo+
foobar
~~~

在程式輸出中,上面的紅色字母會反白顯示。"~~~" 是為了方便使用文字瀏覽器 (text-based browser) 的用戶而加的。

多試試幾個輸入吧。

str> abc012dbcd555
pat> \d
abc012dbcd555
   ~~~    ~~~

如果對結果感到驚訝,請參考上面的表格:\d 與字母 d 完全沒有關係,只是與數字匹配。

那如果有一個以上的結果符合樣式呢?

str> foozboozer
pat> f.*z
foozboozer
~~~~~~~~

符合的是 foozbooz,而不僅僅是 fooz,正規表示式會與最長的子字串匹配。

這是用來隔離冒號區隔時間欄位 (colon-delimited time field) 的樣式。

str> Wed Feb  7 08:58:04 JST 1996
pat> [0-9]+:[0-9]+(:[0-9]+)?
Wed Feb  7 08:58:04 JST 1996
           ~~~~~~~~

就正規表示式而言,"=~" 是匹配的運算子 (operator),如果發現符合的話,就會在字串中傳回位置,沒有符合就會傳回 nil

ruby> "abcdef" =~ /d/
   3
ruby> "aaaaaa" =~ /d/
   nil

Copyright (c) 2005-2008 Mark Slagell,中文翻譯 Ruby Taiwan 社群

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.

A copy of the license is included in the section entitled "GNU Free Documentation License."