文章目錄
- 引言
- Regex使用曆史簡介
- 簡單運算式
- 限定符
- 元字元
- 字元類
- 預定義的集合元字元
- 運算式樣本
- ASP.NET 中的驗證
- Regex API
- 免費工具
- 進階主題
引言
Microsoft.NET Framework 對Regex的支援是一流的,甚至在 Microsoft ASP.NET 中也有依賴Regex語言的控制項。本文介紹了深入學習Regex的基礎知識和推薦內容。
本文主要面向對Regex知之甚少或沒有使用經 驗,但卻熟悉 ASP.NET、可藉助 .NET 編程的初學者。此外,希望本文連同 regular expression cheat sheet 成為有Regex使用經驗的開發人員的手頭參考資料或進修資料。本文討論內容如下:
Regex使用曆史簡介
簡單運算式
限定符
元字元
字元類
預定義的集合元字元
運算式樣本詳細內容
ASP.NET 中的驗證
Regex API
免費工具
進階主題概述
小結和其他資源
通常,如果對本文或對Regex有疑問,請訪問 http://www.aspadvice.com/, 通過 regex mailing list 提出問題。編寫此文時其中已有 350 多個訂戶參與。
返 回頁首 Regex使用曆史簡介
正 則運算式設計於五十年代,存在至今。Regex最初用於描述“正則集”,它們是一些神經生理學家研究的模式。Regex最早由數學家 Stephen Kleene 提出,最終由 Ken Thompson 在兩種非常流行的文本公用程式 qed 和 grep 中使用。Jeffrey Friedl 在其著作“Mastering Regular Expressions (2nd edition)”中對此作了進一步闡述。建議那些希望更多瞭解Regex理論和曆史的人看看這本書。
在最近的五十年中,Regex逐漸 從模糊深奧的數學概念發展為在各類工具和軟體包中應用的主要功能。儘管數十年來很多 UNIX 工具都支援Regex,但僅僅是近十年來,它才在大部分 Windows 開發人員工具包中得到體現。在 Microsoft Visual Basic 6 或 Microsoft VBScript 中,即使情況理想,Regex仍難以使用。但隨著.NET Framework 的推行,Regex的支援發展到極點,所有 Microsoft 開發人員和所有 .NET 語言都可以使用Regex。
那麼,Regex究竟是什麼呢?Regex是一種語言,它可以明確描述文本字串中 的模式。除了簡單描述這些模式之外,Regex引擎通常可用於遍曆匹配,並使用模式作為分隔字元來將字串解析為子字串,或以智能方式替換文本或重新設定 文字格式設定。Regex為解決與文本處理有關的許多常見任務提供了有效而簡捷的方式。
在討論Regex時,通常以Regex匹配(或不匹 配)的文本為基礎分析Regex。本文(以及 System.Text.RegularExpressions 類)將在Regex互動操作中引用 3 個參與對象:Regex的“模式”、“輸入”字串和字串內的所有模式的“匹配”。
返 回頁首 簡單運算式
最簡單的正 則運算式大家都已熟悉,即文字字串。特定的字串可通過文字本身加以描述;像 foo 這樣的Regex模式可精確匹配輸入的字串 foo。 在本例中,也將匹配如下輸入:The foo d was quite tasty,如果希望精確匹 配,這可能不是預期結果。
當然,使用Regex匹配等於它自身的精確字串是沒有價值的實現,不能體現Regex的真正作用。假如不尋找 foo,而是尋找以字母 f 開頭的所有單詞,或所有 3 個字母的單詞,那該怎麼辦?目前,這超出了文字字串的合理範圍。我們需要更加深入地研究Regex。下面是一個文字運算式樣本及一些匹配的輸入。
模式 |
輸入(匹配) |
foo |
foo、food、foot、 “There's evil afoot.” |
返 回頁首 限定符
限定符提供了一 種簡單方法,用於指定在模式中允許特定字元或字元集自身重複出現的次數。有 3 個非顯式限定符:
*, 描述“出現 0 或多次”。
+,描述“出現 1 或多次”。
?, 描述“出現 0 或 1 次”。
限定符始終引用限定符前(左邊)的模式,通常是單個字元,除非使用括弧建立模 式組。下面是一些模式樣本及匹配的輸入。
模式 |
輸 入(匹配) |
fo* |
foo、foe、food、fooot、 “forget it”、funny、 puffy |
fo+ |
foo、foe、food、foot、 “forget it” |
fo? |
foo、foe、food、foot、“forget it”、funny、puffy |
除了指定給定模式準確出現 0 或 1 次之外,? 字元還可強制模式或子模式比對數目最少的字元(如果匹配輸入字串中的多個字元)。
除了非顯式限定符(一般叫做限定符,但為區別於下一 組,故稱非顯式限定符)之外,還有顯式限定符。在模式出現次數方面,限定符的概念非常模糊。使用顯式限定符則可準確指定數字、範圍或數字集。顯式限定符位 於所應用的模式的後邊,這一點與正則限定符一樣。顯式限定符使用花括弧 {} 及其中的數字值表示模式出現次數的上下限。例如,x{5} 將準確匹配 5 個 x 字元 (xxxxx)。如果僅指定一個數字,則表示次數上限;如果數字後跟一個逗號,如 x{5,}, 表示匹配任何出現次數大於 4 的 x 字元。下面是一些模式樣本及匹配的輸入。
模 式 |
輸入(匹配) |
ab{2}c |
abbc、aaabbccc |
ab{,2}c |
ac、abc、abbc、aabbcc |
ab{2,3}c |
abbc、abbbc、aabbcc、 aabbbcc |
返 回頁首 元字元
在Regex 中,有一種意義特殊的構造,即元字元。目前已知的元字元有很多,如 *、?、+ 和 {} 字元。其他字元在Regex語言中都有特殊的含義。這些字元包括:$ ^ . [ ( | ) ] 和 \。
.(句點或點)元字元是最簡單但最常用的一個字元。它可匹配任何單字元。如果要指定某些模式可包含任意組合的字元,使用句點非常有用,但 一定要在特定長度範圍內。此外,我們知道運算式將對包含在較長字串中的所有模式進行匹配,假如只需要精確匹配模式,又該怎麼辦?這在驗證方案中經常出 現,例如,要確保使用者輸入的郵遞區號或電話號碼的格式正確。使用 ^ 元字元可指定字串(或行)的開始,使用 $ 元字元可指定字串(或行)的結束。通過將這些字元添加到模式的開始和結束處,可強制模式僅匹配精確匹配的輸入字串。如果 ^ 元字元用在方括弧 [ ] 指定的字元類的開頭,也有特殊的含義。具體內容見下。
\ (反斜線)元字元既 可根據特殊含義“轉義”字元,也可指定預定義集合元字元的執行個體。同樣,具體內容見下。為了在Regex中包括文字樣式的元字元,必須使用反斜線進行“轉 義”。例如,如果要匹配以“c:\”開始的字串,可使用:^c:\\。注意,要使用 ^ 元字元指出字串必須以此模式作為開始,然後用反斜線元字元轉義文字反斜線。
|(管道)元字元用於交替指定,特別用於在 模式中指定“此或彼”。例如,a|b 將匹配包含“a”或“b”的任何輸入內容,這與字元類 [ab] 非常類似。
最後,括弧 ( ) 用於給模式分組。它允許使用限定符讓一個完整模式出現多次。為了便於閱讀,或分開匹配特定的輸入部分,可能允 許分析或重新設定格式。
下面列出元字元的一些使用樣本。
模式 |
輸入(匹配) |
. |
a、b、c、1、2、3 |
.* |
Abc, 123, 任一字元串, 無字元時也匹配 |
^c:\\ |
c:\windows、c:\\\\\、c:\foo.txt、c:\ 後跟任何其他內容 |
abc$ |
abc、123abc、以 abc 結束的任一字元串 |
(abc){2,3} |
abcabc、abcabcabc |
返 回頁首 字元類
字元類是正則表 達式中的“迷你”語言,在方括弧 [ ] 中定義。最簡單的字元類只不過是括弧中的一個字元表,如 [aeiou]。在表達 式中使用字元類時,可在模式的此位置使用其中任何一個字元(但只能使用一個字元,除非使用了限定符)。請注意,不能使用字元類定義單詞或模式,只能定義單 個字元。
要指定任何數值數字,可以使用字元類 [0123456789]。但是,由於這樣使用字元不大方便,所以要通過在 括弧中使用連字號 - 來定義字元的範圍。連字號在字元類中有特殊的含義(不是在Regex中,因此,準確地說它不能叫Regex元字元),且僅在連字號不是第一個字元時,連字 符才在字元類中有特殊含義。要使用連字號指定任何數值數字,可以使用 [0-9]。小寫字母也一樣,可以使用 [a-z], 大寫字母可以使用 [A-Z]。連字號定義的範圍取決於使用的字元集。因此,字元在(例如)ASCII 或 Unicode 表中出現的順序確定了在範圍中包括的字元。如果需要在範圍中包括連字號,將它指定為第一個字元。例如:[-.?] 將匹配 4 個字元中任何一個字元(注意,最後的字元是個空格)。另請注意,Regex元字元在字元類中不做特殊處理,所以這些元字元不需要轉義。考慮到字元類是與其 他Regex語言分開的一種語言,因此字元類有自己的規則和文法。
如果使用字元 ^ 作為字元類的第一個字元來否定此類,也可以匹配字元類成員以外的任何字元。因此,要匹配任何非母音字元,可以使用字元類 [^aAeEiIoOuU]。 注意,如果要否定連字號,應將連字號作為字元類的第二個字元,如 [^-]。記住,^ 在字元類中的作用與它在Regex模 式中的作用完全不同。
下面列出操作中使用的一些字元類。
模式 |
輸入(匹配) |
^b[aeiou]t$ |
Bat、bet、bit、bot、but |
^[0-9]{5}$ |
11111, 12345, 99999 |
^c:\\ |
c:\windows、c:\\\\\、c:\foo.txt、c:\ 後跟任何其他內容 |
abc$ |
abc、123abc、以 abc 結束的任一字元串 |
(abc){2,3} |
abcabc、abcabcabc |
^[^-][0-9]$ |
0、1、2、... (不匹配 -0、-1、 -2 等) |
在 .NET Framework 的下一版中,代碼名“Whidbey”作為一種新功能被添加到字元類中,稱作字元類差 (character class subtraction)。它的主要作用是,允許從一個字元類中減去另一個字元類,可提供更可讀的方式描述某些模式。該規範可通過以下地址訪問:http://www.gotdotnet.com/team/clr/bcl/TechArticles/techarticles/Specs/Regex/CharacterClassSubtraction.doc。 它的文法類似 [a-z-[aeiou]],匹配所有的小寫輔音字母。
返 回頁首 預定義的集合元字元
使 用目前提供的工具可以完成很多工作。但是,要使用 [0-9] 表示模式中的每個數值數字,或(更糟)使用 [0 -9a -zA-Z]表示任何字母數字字元,還有一段相當漫長的過程。為了減輕處理這些常用但冗長模式的痛苦,事先定義了預定義元字元集合。正則 運算式的不同實現定義了不同的預定義元字元集合,下面描述的預定義元字元集合在 .NET Framework 中得到 System.Text.RegularExpressions API 的支援。這些預定義元字元的標準文法是,在反斜線 \ 後跟一個或多個字元。多數預定義元字元只有一個字元,它們的使用很容易,是冗長字元類的理想替代字元。以下是兩個樣本:\d 匹配所有數值數字,\w 匹配所有單詞字元(字母數字加底線)。例外情況是一些特定字元代碼匹配,此時必須指定所匹配字元的地址,如 \u000D 將匹配 Unicode 斷行符號符。下面列出一些最常用的字元類及其等效的元字元。
元字元 |
等效字元類 |
\a |
匹配鈴聲(警報);\u0007 |
\b |
匹 配字元類外的字邊界,它匹配退格字元,\u0008 |
\t |
匹 配製表符,\u0009 |
\r |
匹配斷行符號 符,\u000D |
\w |
匹配垂直製表 符,\u000B |
\f |
匹配換頁符,\u000C |
\n |
匹配新行,\u000A |
\e |
匹配轉義符,\u001B |
\040 |
匹配 3 位 8 進位 ASCII 字元。\040 表示空格(十進位數 32)。 |
\x20 |
使用 2 位 16 進位數匹配 ASCII 字元。此例中,\x2- 表示空格。 |
\cC |
匹配 ASCII 控制字元,此例中是 ctrl-C。 |
\u0020 |
使用 4 位 16 進位數匹配 Unicode 字元。此例中 \u0020 是空格。 |
\* |
不代表預定義字元類的任一字元都只作為該字元本身對待。因此,\* 等同於 \x 2A(是文字 *,不是 * 元字元)。 |
\p{name} |
匹配已命名字元類“name”中的任一字元。支援名稱是 Unicode 組和區塊範圍。例如,Ll、Nd、Z、IsGreek、IsBoxDrawing 和 Sc(貨幣)。 |
\p{name} |
匹配已命名字元類“name”中不包括的文本。 |
\w |
匹配任意單詞字元。對於非 Unicode 和 ECMAScript 實現,這等同於 [a-zA-Z_0-9]。 在 Unicode 類別中,這等同於 [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}]。 |
\W |
\w 的否定,等效於 ECMAScript 相容集合 [^a-zA-Z_0-9] 或 Unicode 字元類別 [^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}]。 |
\s |
匹配任意空白地區字元。等效於 Unicode 字元類 [\f\n\r\t\v\x85\p{Z}]。 如果使用 ECMAScript 選項指定 ECMAScript 相容方式,\s 等效於 [ \f\n\r\t\v] (請注意前置空 格)。 |
\S |
匹配任意非空白地區字元。等效於 Unicode 字元類別 [^\f\n\r\t\v\x85\p{Z}]。如果使用 ECMAScript 選項指定 ECMAScript 相容方式,\S 等效於 [^ \f\n\r\t\v] (請注意 ^ 後的空格)。 |
\d |
匹配任意十進位數字。在 ECMAScript 方式下,等效於 Unicode 的 [\p{Nd}]、非 Unicode 的 [0-9]。 |
\D |
匹配任意非十進位數字。在 ECMAScript 方式下,等效於 Unicode 的 [\p{Nd}]、 非 Unicode 的 [^0-9]。 |
返 回頁首 運算式樣本
很多人都喜 歡通過樣本學習,下面即提供一些運算式樣本。要擷取更多樣本,請訪問以下地址中的Regex線上資料庫:http://www.regexlib.com/。
模式 |
說明 |
^\d{5}$ |
5 個數值數字,如美國郵遞區號。 |
^(\d{5})|(\d{5}-\d{4}$ |
5 個數值數字或 5 個數字-短劃線-4 個數字。匹配 5 位元字格式的美國郵遞區號,或 5 位元字 + 4 位元字格式的美國郵遞區號。 |
^(\d{5}(-\d{4})?$ |
與前一個相同,但更有效。使用 ? 可使模式中的 4 位元字成為可選部分,而不是要求分別比較不同的兩個模式(通過另一種方式)。 |
^[+-]?\d+(\.\d+)?$ |
匹配任意有可選符號的實數。 |
^[+-]?\d*\.?\d*$ |
與上一個相同,但也匹配Null 字元串。 |
^(20|21|22|23|[01]\d)[0-5]\d$ |
匹配 24 小時制時間值。 |
/\*.*\*/ |
匹配 C 語言風格的注釋 /* ... */ |
返 回頁首 ASP.NET 中的驗證
ASP.NET 提供了一套驗證控制項,與使用舊的(或願意的話使用傳統的) ASP 處理任務相比,驗證控制項使在 Web 表單上驗證輸入變得非常容易。其中一個非常有效驗證器是 RegularExpressionValidator,如您所料,它允許您提 供必須匹配輸入的Regex來驗證輸入。設定控制項的 ValidationExpression 屬性可指定Regex的模式。下面顯示了驗證郵政代碼欄位的驗證程式:
<asp:RegularExpressionValidator runat="server" id="ZipCodeValidator"
ControlToValidate="ZipCodeTextBox" ErrorMessage="Invalid ZIP code
format; format should be either 12345 or 12345-6789."
ValidationExpression="(\d{5}(-\d{4})?" />
使用 RegularExpressionValidator 要注意幾個問題:
決 不要使用驗證程式要驗證的控制項中的Null 字元串來啟用驗證器。只有 RequiredFieldValidator 才可以捕獲Null 字元串。
您無需指定匹配字元的開始與結尾(^ 和$)- 它們是事先假設的。如果添加了開始與結尾,也沒有任何影響,不需要這樣做。
對於所有驗證控制項來說,必須在用戶端 以及伺服器端進行驗證。如果Regex不是 ECMAScript 相容方式,用戶端驗證將失敗。為了避免這種情況,確保運算式是 ECMAScript 相容方式,否則只在伺服器端進行控制項驗證。
返 回頁首 Regex API
除 了 ASP.NET 驗證控制項,在.NET 中使用Regex的大多數情況都要使用 System.Text.RegularExpressions 命名空間中發現的類。特別是那些您希望熟悉的主類 Regex、Match 和 MatchCollection。
順便說一下,Regex縮寫樣式 regex 的發音究竟是 /reg-eks/ 還是 /rej-eks/,還有一些爭議。本人傾向於後者,但兩種發音都有專家贊同,所以選擇哪個發音由您自己決定。
Regex 類有大量的方法和屬性,如果您以前沒有用過它,可能會感到無所適從。下面匯總了一些最常用的方法:
方法 |
說明 |
Escape / Unescape |
字串中的轉義元字元,用作運算式中的文字。 |
IsMatch |
如果Regex在輸入字串中發現匹配,返回“Ture”。 |
Match |
如果在輸入字串中發現匹配,則返回匹配對象。 |
Matches |
如果在輸入字串中發現包含任何或全部匹配,則返回匹配集合對象。 |
Replace |
用給定的替換字串替換輸入字串中的匹 配。 |
Split |
將輸入字串拆分成用Regex匹 配分開的數組元素時,返回數組字串。 |
除 了指定很多方法外,還有一些選項可以指定,通常在 Regex 物件建構函數中。由於這些選項是位屏蔽的一部分,或許可以同時指定這些選項(如,可以同時指定 Multiline 和 Singleline)。
方法 |
說明 |
Compiled |
當在迴圈中執行許多匹配操作時使用此選項。這可以節省每一迴圈的分析運算式步 驟。 |
Multiline |
它與輸入字串中的行數沒 有關係。確切地說,它只修改 ^ 和 $ 的方式,以便匹配行開始 (BOL) 和行結尾 (EOL),而不是匹配整個輸入字串的開始和結尾。 |
IgnoreCase |
使模式在匹配搜尋字串時忽略大小寫。 |
IgnorePatternWhitespace |
允許根據需要在模式中包括任意數量的空白地區,也支援使用 (?# 注釋 #) 文法在模式中加入注釋。 |
SingleLine |
它與輸入字串中的行數沒有關係。更確切地說,它 將導致 .(句點)元字元匹配任一字元,而不是除 \n 之外的任一字元(預設情況)。 |
使用Regex常執行的操作包括:驗證、匹配和替換。大多數情況下,可以使用 Regex 類的靜態方法完成這些操作,不需要執行個體化 Regex 類本身。要執行驗證,全部要做的就是必建或找到正確的運算式,然後使用 Regex 類的 IsMatch() 方法將運算式應用到輸入字串中。例如,下面的函數示範了如何使用Regex驗證郵遞區號:
private void ValidateZipButton_Click(object sender, System.EventArgs e)
{
String ZipRegex = @"^\d{5}$";
if(Regex.IsMatch(ZipTextBox.Text, ZipRegex))
{
ResultLabel.Text = "ZIP is valid!";
}
else
{
ResultLabel.Text = "ZIP is invalid!";
}
}
類似的,可以使用靜態 Replace() 方法將匹配替換為特定字串,如下所示:
String newText = Regex.Replace(inputString, pattern, replacementText);
最後,可以使用如下代碼遍曆輸入字串的匹配集合:
private void MatchButton_Click(object sender, System.EventArgs e)
{
MatchCollection matches = Regex.Matches(SearchStringTextBox.Text,
MatchExpressionTextBox.Text);
MatchCountLabel.Text = matches.Count.ToString();
MatchesLabel.Text = "";
foreach(Match match in matches)
{
MatchesLabel.Text += "Found" + match.ToString() + " at
position " + match.Index + ".<br>";
}
}
通常,在您需要指定預設以外的方式時,需要執行個體化 Regex 類的執行個體。特別是在設定選項時。例如,要建立忽略大小寫和模式空白地區的 Regex 執行個體,然後檢索與該運算式匹配的集合,則應使用如下代碼:
Regex re = new Regex(pattern,
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
MatchCollection mc = re.Matches(inputString);
本文的下載檔案中包括這些樣本的完整使用版本,與簡單 ASP.NET 頁中的一樣。
返 回頁首 免費工具
Regulator (http://royo.is-a-geek.com/iserializable/regulator/) - 一種在用戶端啟動並執行Regex測試載入器,通過 Web 服務與 RegexLib 緊密整合,提供對“匹配”、“拆分”和“替換”等的支援。包括效能分析和文法反白功能。
RegexDesigner.NET (http://www.sellsbrothers.com/tools/) - 一種功能強大的可視工具,可協助構造並測試Regex。它可產生 C# 和/或 VB.NET 代碼和已編譯彙編代碼,協助您將運算式整合到應用程式中。
Regular Expression Workbench (v2.0) (http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=C712F2DF-B026-4D58-8961-4EE2729D7322) - Eric Gunnerson 開發的工具,用於建立、測試和研究Regex。具有“Examine-o-matic”功能,允許將滑鼠移至上方在Regex的上方,對其含義進行解碼。
返 回頁首 進階主題
Regex有 兩個不得不說的功能,一個是“命名組”,另一個是“四向處理”(lookaround processing)。由於這些功能很少使用,此處只簡單闡述一下。
使用命名組,您可單獨命名匹配組,然後在運算式中使用程式語言引用 這些組。如果結合 Replace 方法重新設定輸入字串的格式(通過重新排列順序、替換輸入字串中的元素),這個功能特別有效。例如,假設日期使用 MM/DD/YYYY 格式的字串,而您希望日期格式是 DD-MM-YYYY。此時,可編寫一個運算式捕獲第一種格式,遍曆它的匹配集合,並分析每個字串,然後使用字串操作建立替換字串。這需要大量的代 碼和大量的處理。如果使用命名組,您可完成同樣的任務,具體見下:
String MDYToDMY(String input)
{
return Regex.Replace(intput, @"\b(?<month>\d{1,2})/(?<day>\d{1,2}/(?<year>\d{4})\b", "${day}-
${month}-${year}");
}
您還可以按編號或按名稱引用組。在任何情況下,這種引用通稱作“反向引用”。另一個經常使用反向引用的場合在匹配運算式本身,如下這 個運算式用於尋找重複的字母:[a-z]\1。它將匹配“aa”、“bb”、“cc”,但它不同於 [a-z]{2} 或 [a-z][a-z], 後兩者是等效的,後兩者允許匹配“ab”或“ac”或任何其他兩個字母的組合。反向引用允許運算式記住運算式已經分析並匹配過的輸入字串中的部分字元。
“四向處理”指很多Regex引擎所支援的正負 Lookahead 和 Lookbehind 功能。並不是所有的Regex引擎都支援驗證四向處理。這些構造不使用字元,即使它們可以匹配字元。有些模式可能在不使用四向處理的情況下無法描述。特別 是當模式中存在的一部分依賴於另一部分,如果不使用四向處理,則不能描述這樣的模式。下面介紹了每個四向處理的文法。
文法 |
說明 |
(?=...) |
正 Lookahead |
(?!...) |
負 Lookahead |
(?<=...) |
正 Lookbehind |
(?<!...) |
負 Lookbehind |
密碼 驗證是必需四向處理的一個樣本。假定在密碼限制中,密碼必須介於 4 到 8 個字元,且必須至少包含一個數字。為此,您可以僅在匹配中測試 \d, 然後使用字串操作來測試長度。但要在Regex中實現這一切,必須使用 Lookahead。特別是正 lookahead,如下所示:^(?=.*\d).{4,8}$