經驗總結談VB.NET正則表達式匹配
正則表達式大家熟悉嗎?了解多少,在這里我們就正則表達式來深入的研究一下吧。本文將關于使用?*或+進行重復、使用“.”匹配幾乎任意字符 、字符串開始和結束的錨定、單詞邊界四方面進行分析。
1.使用?*或+進行重復
?:告訴引擎匹配前導字符0次或一次。事實上是表示前導字符是可選的。
+:告訴引擎匹配前導字符1次或多次
*:告訴引擎匹配前導字符0次或多次 <[A-Za-z][A-Za-z0-9]*> 匹配沒有屬性的HTML標簽,“ <”以及“> ”是文字符號。***個字符集匹配一個字母,第二個字符集匹配一個字母或數字。我們似乎也可以用 <[A-Za-z0-9]+> 。但是它會匹配 <1> 。但是這個正則表達式在你知道你要搜索的字符串不包含類似的無效標簽時還是足夠有效的。
限制性重復許多現代的正則表達式實現,都允許你定義對一個字符重復多少次。詞法是:{min,max}。min和max都是非負整數。如果逗號有而max被忽略了,則max沒有限制。如果逗號和max都被忽略了,則重復min次。因此{0,}和*一樣,{1,}和+的作用一樣。你可以用 < <\b[1-9][0-9]{3}\b> > 匹配1000~9999之間的數字(“\b”表示單詞邊界)。 < <\b[1-9][0-9]{2,4}\b> > 匹配一個在100~99999之間的數字。
注意貪婪性假設你想用一個VB.NET正則表達式匹配一個HTML標簽。你知道輸入將會是一個有效的HTML文件,因此正則表達式不需要排除那些無效的標簽。所以如果是在兩個尖括號之間的內容,就應該是一個HTML標簽。許多正則表達式的新手會首先想到用正則表達式 < < <.+> > > ,他們會很驚訝的發現,對于測試字符串,“Thisisa first test”,你可能期望會返回 ,然后繼續進行匹配的時候,返回 。但事實是不會。VB.NET正則表達式匹配“ first ”。很顯然這不是我們想要的結果。原因在于“+”是貪婪的。也就是說,“+”會導致正則表達式引擎試圖盡可能的重復前導字符。只有當這種重復會引起整個VB.NET正則表達式匹配失敗的情況下,引擎會進行回溯。也就是說,它會放棄***一次的“重復”,然后處理正則表達式余下的部分。和“+”類似,“?*”的重復也是貪婪的。
深入正則表達式引擎內部讓我們來看看正則引擎如何匹配前面的例子。***個記號是“ <”,這是一個文字符號。第二個符號是“.”,匹配了字符“E”,然后“+”一直可以匹配其余的字符,直到一行的結束。然后到了換行符,匹配失敗(“.”不匹配換行符)。于是引擎開始對下一個正則表達式符號進行匹配。也即試圖匹配“> ”。到目前為止,“ <.+”已經匹配了“ first test”。引擎會試圖將“> ”與換行符進行匹配,結果失敗了。于是引擎進行回溯。結果是現在“ <.+”匹配“ first tes”。于是引擎將“> ”與“t”進行匹配。顯然還是會失敗。這個過程繼續,直到“ <.+”匹配“ first ”與“> ”匹配。于是引擎找到了一個匹配“ first ”。記住,正則導向的引擎是“急切的”,所以它會急著報告它找到的***個匹配。而不是繼續回溯,即使可能會有更好的匹配,例如“ ”。所以我們可以看到,由于“+”的貪婪性,使得正則表達式引擎返回了一個最左邊的最長的匹配。
用懶惰性取代貪婪性一個用于修正以上問題的可能方案是用“+”的惰性代替貪婪性。你可以在“+”后面緊跟一個問號“?”來達到這一點。“*”,“{}”和“?”表示的重復也可以用這個方案。因此在上面的例子中我們可以使用“ <.+?> ”。讓我們再來看看正則表達式引擎的處理過程。再一次,正則表達式記號“ <”會匹配字符串的***個“ <”。下一個正則記號是“.”。這次是一個懶惰的“+”來重復上一個字符。這告訴正則引擎,盡可能少的重復上一個字符。因此引擎匹配“.”和字符“E”,然后用“> ”匹配“M”,結果失敗了。引擎會進行回溯,和上一個例子不同,因為是惰性重復,所以引擎是擴展惰性重復而不是減少,于是“ <.+”現在被擴展為“ ”。這次得到了一個成功匹配。引擎于是報告“ ”是一個成功的匹配。整個過程大致如此。
惰性擴展的一個替代方案我們還有一個更好的替代方案。可以用一個貪婪重復與一個取反字符集:“ <[^> ]+> ”。之所以說這是一個更好的方案在于使用惰性重復時,引擎會在找到一個成功匹配前對每一個字符進行回溯。而使用取反字符集則不需要進行回溯。***要記住的是,本教程僅僅談到的是正則導向的引擎。文本導向的引擎是不回溯的。但是同時他們也不支持惰性重復操作。
2.使用“.”匹配幾乎任意字符
在正則表達式中,“.”是最常用的符號之一。不幸的是,它也是最容易被誤用的符號之。“.”匹配一個單個的字符而不用關心被匹配的字符是一什么。唯一的例外是新行符。在本教程中談到的引擎,缺省情況下都是不匹配新行符的。因此在缺省情況下,“.”等于是字符集[^\n\r](Window)或[^\n](Unix)的簡寫。這個例外是因為歷史的原因。因為早期使用正則表達式的工具是基于行的。它們都是一行一行的讀入一個文件,將正則表達式分別應用到每一行上去。在這些工具中,字符串是不包含新行符的。因此“.”也就從不匹配新行符。
現代的工具和語言能夠將正則表達式應用到很大的字符串甚至整個文件上去。本教程討論的所有正則表達式實現都提供一個選項,可以使“.”匹配所有的字符,包括新行符。
在RegexBuddy,EditPadPro或PowerGREP等工具中,你可以簡單的選中“點號匹配新行符”。在Perl中,“.”可以匹配新行符的模式被稱作“單行模式”。很不幸,這是一個很容易混淆的名詞。因為還有所謂“多行模式”。多行模式只影響行首行尾的錨定(anchor),而單行模式只影響“.”。其他語言和正則表達式庫也采用了Perl的術語定義。當在.NETFramework中使用正則表達式類時,你可以用類似下面的語句來激活單行模式:Regex.Match(“string”,”regex”,RegexOptions.SingleLine)
保守的使用點號“.”點號可以說是***大的元字符。它允許你偷懶:用一個點號,就能匹配幾乎所有的字符。但是問題在于,它也常常會匹配不該匹配的字符。我會以一個簡單的例子來說明。讓我們看看如何匹配一個具有“mm/dd/yy”格式的日期,但是我們想允許用戶來選擇分隔符。很快能想到的一個方案是 < <\d\d.\d\d.\d\d> > 。看上去它能匹配日期“02/12/03”。問題在于02512703也會被認為是一個有效的日期。 < <\d\d[-/.]\d\d[-/.]\d\d> > 看上去是一個好一點的解決方案。記住點號在一個字符集里不是元字符。這個方案遠不夠完善,它會匹配“99/99/99”。而 < <[0-1]\d[-/.][0-3]\d[-/.]\d\d> > 又更進一步。盡管他也會匹配“19/39/99”。你想要你的正則表達式達到如何***的程度取決于你想達到什么樣的目的。如果你想校驗用戶輸入,則需要盡可能的***。如果你只是想分析一個已知的源,并且我們知道沒有錯誤的數據,用一個比較好的VB.NET正則表達式匹配你想要搜尋的字符就已經足夠。
3.字符串開始和結束的錨定
錨定和一般的正則表達式符號不同,它不匹配任何字符。相反,他們匹配的是字符之前或之后的位置。“^”匹配一行字符串***個字符前的位置。 < <^a> > 將會匹配字符串“abc”中的a。 < <^b> > 將不會匹配“abc”中的任何字符。類似的,$匹配字符串中***一個字符的后面的位置。所以 <
錨定的應用在編程語言中校驗用戶輸入時,使用錨定是非常重要的。如果你想校驗用戶的輸入為整數,用 < <^\d+$> > 。用戶輸入中,常常會有多余的前導空格或結束空格。你可以用 < <^\s*> > 和 < <\s*$> > 來匹配前導空格或結束空格。
使用“^”和“$”作為行的開始和結束錨定如果你有一個包含了多行的字符串。例如:“firstline\n\rsecondline”(其中\n\r表示一個新行符)。常常需要對每行分別處理而不是整個字符串。因此,幾乎所有的正則表達式引擎都提供一個選項,可以擴展這兩種錨定的含義。“^”可以匹配字串的開始位置(在f之前),以及每一個新行符的后面位置(在\n\r和s之間)。類似的,$會匹配字串的結束位置(***一個e之后),以及每個新行符的前面(在e與\n\r之間)。在.NET中,當你使用如下代碼時,將會定義錨定匹配每一個新行符的前面和后面位置:Regex.Match("string","regex",RegexOptions.Multiline)應用:stringstr=Regex.Replace(Original,"^","> ",RegexOptions.Multiline)--將會在每行的行首插入“> ”。
絕對錨定 < <\A> > 只匹配整個字符串的開始位置, < <\Z> > 只匹配整個字符串的結束位置。即使你使用了“多行模式”, < <\A> > 和 < <\Z> > 也從不匹配新行符。即使\Z和$只匹配字符串的結束位置,仍然有一個例外的情況。如果字符串以新行符結束,則\Z和$將會匹配新行符前面的位置,而不是整個字符串的***面。這個“改進”是由Perl引進的,然后被許多的正則表達式實現所遵循,包括Java,.NET等。如果應用 < <^[a-z]+$> > 到“joe\n”,則匹配結果是“joe”而不是“joe\n”。
在本文中講述了正則表達式中的組與向后引用,先前向后查看,條件測試,單詞邊界,選擇符等表達式及例子,并分析了正則引擎在執行匹配時的內部機理。
4.單詞邊界
元字符 < <\b> > 也是一種對位置進行匹配的“錨”。這種匹配是0長度匹配。有4種位置被認為是“單詞邊界”:
1)在字符串的***個字符前的位置(如果字符串的***個字符是一個“單詞字符”)
2)在字符串的***一個字符后的位置(如果字符串的***一個字符是一個“單詞字符”)
3)在一個“單詞字符”和“非單詞字符”之間,其中“非單詞字符”緊跟在“單詞字符”之后
4)在一個“非單詞字符”和“單詞字符”之間,其中“單詞字符”緊跟在“非單詞字符”后面“單詞字符”是可以用“\w”匹配的字符,“非單詞字符”是可以用“\W”匹配的字符。
在大多數的正則表達式實現中,“單詞字符”通常包括 < <[a-zA-Z0-9_]> > 。例如: < <\b4\b> > 能夠匹配單個的4而不是一個更大數的一部分。這個正則表達式不會匹配“44”中的4。換種說法,幾乎可以說 < <\b> > 匹配一個“字母數字序列”的開始和結束的位置。“單詞邊界”的取反集為 < <\B> > ,他要匹配的位置是兩個“單詞字符”之間或者兩個“非單詞字符”之間的位置。
深入正則表達式引擎內部讓我們看看把正則表達式 < <\bis\b> > 應用到字符串“Thisislandisbeautiful”。引擎先處理符號 < <\b> > 。因為\b是0長度,所以***個字符T前面的位置會被考察。因為T是一個“單詞字符”,而它前面的字符是一個空字符(void),所以\b匹配了單詞邊界。接著 < > 和***個字符“T”匹配失敗。匹配過程繼續進行,直到第五個空格符,和第四個字符“s”之間又匹配了 < <\b> > 。然而空格符和 < > 不匹配。繼續向后,到了第六個字符“i”,和第五個空格字符之間匹配了 < <\b> > ,然后 <
【編輯推薦】