2011年7月6日 星期三

Regular Expression 詳論

Regular Expression 到底是什麼?

簡單的說,所謂的 Regular Expression (正規表示式,經常被簡寫為 Regex) 的最主要目的,在於使用一組特定的表示式,來驗證一段字串是否符合某個特定的樣式(Pattern)。舉例來說,當你希望使用者輸入他的 Email 位址時,你要使用什麼方法讓程式來判斷使用者確實輸入正確的格式,而沒有輸入亂七八糟的東西呢(當然,他或許會輸入偽造的、假的 Email,但這是另一個議題了)? 又例如,如果你需要使用者輸入他的電話號碼,你又如何確定他輸入了正確的樣式(像 02-12345678), 而不是隨心所欲的亂敲一通?


當然, 你不能期望 Regular Expression 能幫你篩選類似故意假造或亂寫的電子郵件或電話號碼,你只能使用它來防止使用者因為沒有依循你所設定的「樣式」來輸入資料。以電話號碼為例,如果你規定輸入的樣式是 02-12345678 或 07-1234567,那麼以下的數字樣式都是錯的:
  • 0212345678 (少了一橫槓)
  • 12345678 (少了區域碼)
  • (02)12345678 (使用括弧而非橫槓)
  • 02-1234-5678 (多了一個橫槓)
  • +886-2-1234-5678 (並非你指定的格式)
  • 206-1234-5678 (非本國電話格式)

如果你要撰寫程式來過濾以上的問題,恐怕要下不少判斷式,而且不見得能把所有的錯誤情況全部掌握。但是如果使用 Regular Expression ,再配合 Validators,通常都可以輕鬆的解決。

曾經有人告訴我,輸入樣式不對,有什麼關係?反正這些電話號碼都是給人看的,電腦不需要理解,人能理解就好啦!

我的回答是「話是沒錯,但是你確定你的電腦系統永遠都不需要能夠正確的判斷這些電話號碼嗎?」如果你的資料庫中每一筆電話格式都是正確的、一致的,那麼我只要寫幾行程式,甚至不用寫程式,就可以做到以下幾件事情:

  • 把這些電話與它的區域碼分離出來。如此也可以據以判斷使用者的區域分佈。
  • 能使用更正確的方法, 以程式判斷使用者是不是輸入了重複的號碼。
  • 可以將號碼進行正確的排序。
  • 未來如果遭遇任何電話系統的更改(例如從前將號碼從七碼改成八碼,或在前面加上 2),都有辦法寫程式對整個資料庫內的所有號碼進行變更,無需人工作業一筆一筆處理。
  • 同樣的,未來可以視需要,將全部號碼進行變更,例如將 02 改成 +886-2。
  • 方便以程式方法進行正確的比對。
這些事情都是幾乎不需要代價的舉手之勞,只要你把 Validator 放在 TextBox 後面,並餵給它正確的 Regular Expression 就行了。為求萬全, 我們最好在伺服器端同樣使用 Regular Expression 再多做一次驗證。




Regex 所使用的符號彙整

那麼 Regular Expression 是如何表示的呢?請看下面的 Notation 列表:
記號說明
字元代表該字元, 例如輸入 a 就代表那個地方應該出現 a 這個字元
^限制字串必須出現於行首, 例如 ^a 表這串字必須以 a 開頭; 如果 a 出現在其它地方, 都不算數
$限制字串必須出現於行末, 例如 a$ 表這串字必須以 a 結尾; 如果 a 出現在其它地方, 都不算數
\將特殊字元還原成字面意義的字元, 例如 \( 代表 ( 這個符號, \\ 代表 \ 這個符號; 這種表示法適用於 (, ), [, ] 等在 Regex 有特殊意義的字元
^某字元以外的任何字元, 必須包在中括號裡面。例如 [^a] 表示 a 除外的任何字元或符號, [^a\t] 表示 a 和 tab 除外的任何字元或符號
-字元集合中可使用 - 來指定字元的區間, 必須包在中括號裡面。例如 [a-z] 表示從 a 到 z 的英文小寫字元, [1-3] 表示從 1 到 3 這三個數字之一
+其前的字元或字元集合出現一次或一次以上, 例如 a+
?其前的字元或字元集合可出現一次或不出現, 例如 a?
*其前的字元或字元集合可出現任何次數或不出現, 例如 a*
(...) 用以括住一群字元,且將之視成一個集合, 通常用來集合表示多個檢核式
{n}重複 n 次
{n,m}重複 n 到 m 次
{n,}至少重複 n 次
[]其中之一字元可出現可不出現,例如 [abc] 表示不論出現 a 或 b 或 c 都算符合
|代表「或」, 例如 (Sun|Mon|Tue|Wed|Thu|Fri|Sat), (日|一|二|三|四|五|六) ; 必須以左右括號括住
. (句點符號)代表除了換行符號 (\n) 以外的任一字元。如果要包括換行符號,請使用 [\s\S]
\w (\W)代表任何英文(以外的) 字元 - 請注意, 數字字元也被承認
\s (\S)代表空白 (以外的) 字元
\d (\D)代表數字 (以外的) 字元
\b (\B)代表位於文字邊界的 (以外的) 字元, 例如 \bA 可以檢核出 AB, A\b 可以檢核出 BA, \bAA\b 可以檢核出 AA
\r代表換行字元 (或稱 CR, Carriage Return)
\n代表換行字元 (或稱 LF, Line Feed; 通常和 \r 一同出現, 所以一般以 \r\n 代表換行, 但根據我的測試, 無論使用 \r 或 \n 或 \r\n 都會得到相同的結果, 但唯獨不能寫成 \n\r, 但建議使用 \r?\n)
\t代表 TAB 字元 (或稱 HT, Horizontal Tab)
\(代表左括號
\)代表右括號
\x以十六進位字元碼代表某個字元; 例如 [\x21-\x7E] 可代表所有看得到的字元 ([\x20-\x7E] 則包括空白字元)。不過注意 \x 之後要使用兩個數字, 不足兩個數字者請補 0, 例如 \x01
\1, \2...(Backreference Constructs) 表示出現過的群組; 例如 "(\d)(\D)" 樣式中有兩個群組, 若使用 "(\d)(\D)\1" 可檢出 "2A3"; 若使用 "(\d)(\D)\2+" 則可檢出 "2AB"; 餘此類推
\k<name>同上, 但適用於命名的群組; 例如 "(?<Digit>\d)(?<NonDigit>\D)\k<Digit>" 亦可檢出 "2A3"
\p{Lu} (\P{Lu})檢出大寫(非大寫)的字母, 例如 (?-i:\p{Lu}) 可檢出字串中所有大寫字母, 而 (?-i:\P{Lu}) 可檢出所有非大寫 (包括數字、空白等) 的字母
關於更詳細的用法,你可以使用 Google 搜尋以找到更多的相關資料。
如果你想要練習 Regex 的話,你可以在 RegExLib.com 練習。先進入網頁,把畫面往下捲一點,在 Source 方塊中打進測試文字(例如 abc),然後在稍下方的 Pattern 方塊中打進你自己定的 Regex 樣式(例如 \w{3}),按 Submit 按鈕,再稍等一下子,在畫面的最下面就會出現 Match 或是 No match,表示正確或是錯誤。 






原始文章出自於此

沒有留言:

張貼留言