| ·深入Regex引擎內部 讓我們來看看正則引擎如何匹配前面的例子。第一個記號是“<”,這是一個文字元號。第二個符號是“.”,匹配了字元“E”,然後“+”一直可以匹配其餘的字元,直到一行的結束。然後到了分行符號,匹配失敗(“.”不匹配分行符號)。於是引擎開始對下一個Regex符號進行匹配。也即試圖匹配“>”。到目前為止,“<.+”已經匹配了“<EM>first</EM> test”。引擎會試圖將“>”與分行符號進行匹配,結果失敗了。於是引擎進行回溯。結果是現在“<.+”匹配“<EM>first</EM> tes”。於是引擎將“>”與“t”進行匹配。顯然還是會失敗。這個過程繼續,直到“<.+”匹配“<EM>first</EM”,“>”與“>”匹配。於是引擎找到了一個匹配“<EM>first</EM>”。記住,正則導向的引擎是“急切的”,所以它會急著報告它找到的第一個匹配。而不是繼續回溯,即使可能會有更好的匹配,例如“<EM>”。所以我們可以看到,由於“+”的貪婪性,使得Regex引擎返回了一個最左邊的最長的匹配。 ·用懶惰性取代貪婪性 一個用於修正以上問題的可能方案是用“+”的惰性代替貪婪性。你可以在“+”後面緊跟一個問號“?”來達到這一點。“*”,“{}”和“?”表示的重複也可以用這個方案。因此在上面的例子中我們可以使用“<.+?>”。讓我們再來看看Regex引擎的處理過程。 再一次,Regex記號“<”會匹配字串的第一個“<”。下一個正則記號是“.”。這次是一個懶惰的“+”來重複上一個字元。這告訴正則引擎,儘可能少的重複上一個字元。因此引擎匹配“.”和字元“E”,然後用“>”匹配“M”,結果失敗了。引擎會進行回溯,和上一個例子不同,因為是惰性重複,所以引擎是擴充惰性重複而不是減少,於是“<.+”現在被擴充為“<EM”。引擎繼續匹配下一個記號“>”。這次得到了一個成功匹配。引擎於是報告“<EM>”是一個成功的匹配。整個過程大致如此。 ·惰性擴充的一個替代方案 我們還有一個更好的替代方案。可以用一個窮盡重複與一個取反字元集:“<[^>]+>”。之所以說這是一個更好的方案在於使用惰性重複時,引擎會在找到一個成功匹配前對每一個字元進行回溯。而使用取反字元集則不需要進行回溯。 最後要記住的是,本教程僅僅談到的是正則導向的引擎。文本導向的引擎是不回溯的。但是同時他們也不支援惰性重複操作。 7.使用“.”匹配幾乎任一字元 在Regex中,“.”是最常用的符號之一。不幸的是,它也是最容易被誤用的符號之一。 “.”匹配一個單個的字元而不用關心被匹配的字元是什麼。唯一的例外是新行符。在本教程中談到的引擎,預設情況下都是不匹配新行符的。因此在預設情況下,“.”等於是字元集[^/n/r](Window)或[^/n]( Unix)的簡寫。 這個例外是因為曆史的原因。因為早期使用Regex的工具是基於行的。它們都是一行一行的讀入一個檔案,將Regex分別應用到每一行上去。在這些工具中,字串是不包含新行符的。因此“.”也就從不匹配新行符。 現代的工具和語言能夠將Regex應用到很大的字串甚至整個檔案上去。本教程討論的所有Regex實現都提供一個選項,可以使“.”匹配所有的字元,包括新行符。在RegexBuddy, EditPad Pro或PowerGREP等工具中,你可以簡單的選中“點號匹配新行符”。在Perl中,“.”可以匹配新行符的模式被稱作“單行模式”。很不幸,這是一個很容易混淆的名詞。因為還有所謂“多行模式”。多行模式隻影響行首行尾的錨定(anchor),而單行模式隻影響“.”。 其他語言和Regex庫也採用了Perl的術語定義。當在.NET Framework中使用Regex類時,你可以用類似下面的語句來啟用單行模式: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”。你想要你的Regex達到如何完美的程度取決於你想達到什麼樣的目的。如果你想校正使用者輸入,則需要儘可能的完美。如果你只是想分析一個已知的源,並且我們知道沒有錯誤的資料,用一個比較好的Regex來匹配你想要搜尋的字元就已經足夠。 8.字串開始和結束的錨定 錨定和一般的Regex符號不同,它不匹配任何字元。相反,他們匹配的是字元之前或之後的位置。“^”匹配一行字串第一個字元前的位置。<<^a>>將會匹配字串“abc”中的a。<<^b>>將不會匹配“abc”中的任何字元。 類似的,$匹配字串中最後一個字元的後面的位置。所以<<c$>>匹配“abc”中的c。 ·錨定的應用 在程式設計語言中校正使用者輸入時,使用錨定是非常重要的。如果你想校正使用者的輸入為整數,用<<^/d+$>>。 使用者輸入中,常常會有多餘的前置空格或結束空格。你可以用<<^/s*>>和<</s*$>>來匹配前置空格或結束空格。 ·使用“^”和“$”作為行的開始和結束錨定 如果你有一個包含了多行的字串。例如:“first line/n/rsecond line”(其中/n/r表示一個新行符)。常常需要對每行分別處理而不是整個字串。因此,幾乎所有的Regex引擎都提供一個選項,可以擴充這兩種錨定的含義。“^”可以匹配字串的開始位置(在f之前),以及每一個新行符的後面位置(在/n/r和s之間)。類似的,$會匹配字串的結束位置(最後一個e之後),以及每個新行符的前面(在e與/n/r之間)。 在.NET中,當你使用如下代碼時,將會定義錨定匹配每一個新行符的前面和後面位置:Regex.Match("string", "regex", RegexOptions.Multiline) 應用:string str = Regex.Replace(Original, "^", "> ", RegexOptions.Multiline)--將會在每行的行首插入“> ”。 · 絕對錨定 <</A>>只匹配整個字串的開始位置,<</Z>>只匹配整個字串的結束位置。即使你使用了“多行模式”,<</A>>和<</Z>>也從不匹配新行符。 即使/Z和$只匹配字串的結束位置,仍然有一個例外的情況。如果字串以新行符結束,則/Z和$將會匹配新行符前面的位置,而不是整個字串的最後面。這個“改進”是由Perl引進的,然後被許多的Regex實現所遵循,包括Java,.NET等。如果應用<<^[a-z]+$>>到“joe/n”,則匹配結果是“joe”而不是“joe/n”。 |