Regex 編輯 Regex,又稱 正規標記法、 常規標記法(英語:Regular Expression,在代碼中常簡寫為regex、regexp或RE),電腦科學的一個概念。Regex使用單個字串來描述、匹配一系列符合某個句法規則的 字串。在很多 文字編輯器裡,Regex通常被用來檢索、替換那些符合某個模式的文本。 許多程式設計語言都支援利用Regex進行字串操作。例如,在 Perl中就內建了一個功能強大的Regex引擎。Regex這個概念最初是由 Unix中的工具軟體(例如sed和 grep)普及開的。Regex通常縮寫成“regex”, 單數有regexp、regex, 複數有regexps、regexes、regexen。
起源編輯 Regex [1] 的“鼻祖”或許可一直追溯到科學家對人類神經系統工作原理的早期研究。美國新澤西州的Warren McCulloch和出生在美國底特律的Walter Pitts這兩位神經生理方面的科學家,研究出了一種用數學方式來描述神經網路的新方法,他們創造性地將神經系統中的神經元描述成了小而簡單的自動控制元,從而作出了一項偉大的工作革新。 在1956 年,出生在被 馬克·吐溫(Mark Twain)稱為“美國最美麗的城市之一”的 哈特福德市的一位名叫Stephen Kleene的數學科學家,他在Warren McCulloch和Walter Pitts早期工作的基礎之上,發表了一篇題目是《神經網事件的標記法》的論文,利用稱之為正則集合的數學符號來描述此模型,引入了Regex的概念。Regex被作為用來描述其稱之為“正則集的代數”的一種運算式,因而採用了“Regex”這個術語。 之後一段時間,人們發現可以將這一工作成果應用於其他方面。Ken Thompson就把這一成果應用於計算 搜尋演算法的一些早期研究,Ken Thompson是 Unix的主要發明人,也就是大名鼎鼎的Unix之父。Unix之父將此 符號系統引入編輯器QED,然後是Unix上的編輯器ed,並最終引入grep。Jeffrey Friedl 在其著作《Mastering Regular Expressions (2nd edition)》(中文版譯作:精通Regex,已出到第三版)中對此作了進一步闡述講解,如果你希望更多瞭解Regex理論和曆史,推薦你看看這本書。 自此以後,Regex被廣泛地應用到各種UNIX或類似於UNIX的工具中,如大家熟知的 Perl。Perl的Regex源自於Henry Spencer編寫的regex,之後已演化成了pcre(Perl相容RegexPerl Compatible Regular Expressions),pcre是一個由Philip Hazel開發的、為很多現代工具所使用的庫。Regex的第一個實用應用程式即為Unix中的 qed 編輯器。 然後,Regex在各種電腦語言或各種應用領域得到了廣大的應用和發展,演變成為電腦技術森林中的一隻形神美麗且聲音動聽的百靈鳥。 以上是關於Regex的起源和發展的曆史描述,如今Regex在基於文本的 編輯器和搜尋工具中依然佔據著一個非常重要的地位。 在最近的六十年中,Regex逐漸從模糊而深奧的數學概念,發展成為在電腦各類工具和軟體包應用中的主要功能。不僅僅眾多UNIX工具支援Regex,近二十年來,在WINDOWS的陣營下,Regex的思想和應用在大部分 Windows 開發人員工具包中得到支援和嵌入應用。從正則式在Microsoft Visual Basic 6 或 Microsoft VBScript到.NET Framework中的探索和發展,WINDOWS系列產品對Regex的支援發展到無與倫比的高度,幾乎所有 Microsoft 開發人員和所有.NET語言都可以使用Regex。如果你是一位接觸電腦語言的工作者,那麼你會在主流作業系統(*nix[Linux, Unix等]、Windows、HP、BeOS等)、主流的開發語言(delphi、 Scala、 PHP、 C#、Java、 C++、Objective-c、Swift、VB、 Javascript、 Ruby以及 Python等)、數以億萬計的各種應用軟體中,都可以看到Regex優美的舞姿。
概念編輯 Regex是對 字串操作的一種邏輯公式,就是用事先定義好的一些特定字元、及這些特定字元的組合,組成一個“規則字串”,這個“規則字串”用來表達對字串的一種過濾邏輯。 給定一個Regex和另一個字串,我們可以達到如下的目的: 1. 給定的字串是否符合Regex的過濾邏輯(稱作“匹配”); 2. 可以通過Regex,從字串中擷取我們想要的特定部分。 Regex的特點是: 1. 靈活性、邏輯性和功能性非常的強; 2. 可以迅速地用極簡單的方式達到字串的複雜控制。 3. 對於剛接觸的人來說,比較晦澀難懂。 由於Regex主要 應用對象是文本,因此它在各種 文字編輯器場合都有應用,小到著名編輯器EditPlus,大到Microsoft Word、Visual Studio等大型編輯器,都可以使用Regex來處理常值內容。
引擎編輯 正則引擎主要可以分為兩大類:一種是DFA,一種是NFA。這兩種引擎都有了很久的曆史(至今二十多年),當中也由這兩種引擎產生了很多變體。於是POSIX的出台規避了不必要變體的繼續產生。這樣一來,主流的正則引擎又分為3類:一、DFA,二、傳統型NFA,三、POSIX NFA。 DFA 引擎線上性時狀態下執行,因為它們不要求回溯(並因此它們永遠不測試相同的字元兩次)。DFA 引擎還可以確保匹配最長的可能的字串。但是,因為 DFA 引擎只包含有限的狀態,所以它不能匹配具有反向引用的模式;並且因為它不構造顯示擴充,所以它不可以捕獲子運算式。 傳統的 NFA 引擎運行所謂的“貪婪的”匹配 回溯演算法,以指定已排序的測試Regex的所有可能的擴充並接受第一個匹配項。因為傳統的 NFA 構造Regex的特定擴充以獲得成功的匹配,所以它可以捕獲子運算式匹配和匹配的反向引用。但是,因為傳統的 NFA 回溯,所以它可以訪問完全相同的狀態多次(如果通過不同的路徑到達該狀態)。因此,在最壞情況下,它的執行速度可能非常慢。因為傳統的 NFA 接受它找到的第一個匹配,所以它還可能會導致其他(可能更長)匹配未被發現。 POSIX NFA 引擎與傳統的 NFA 引擎類似,不同的一點在於:在它們可以確保已找到了可能的最長的匹配之前,它們將繼續回溯。因此,POSIX NFA 引擎的速度慢於傳統的 NFA 引擎;並且在使用 POSIX NFA 時,您恐怕不會願意在更改回溯搜尋的順序的情況下來支援較短的匹配搜尋,而非較長的匹配搜尋。 使用DFA引擎的程式主要有:awk,egrep,flex,lex,MySQL,Procmail等; 使用傳統型NFA引擎的程式主要有:GNU Emacs,Java,ergp,less,more,.NET語言,PCRE library,Perl,PHP,Python,Ruby,sed,vi; 使用POSIX NFA引擎的程式主要有:mawk,Mortice Kern Systems’ utilities,GNU Emacs(使用時可以明確指定); 也有使用DFA/NFA混合的引擎:GNU awk,GNU grep/egrep,Tcl。 舉例簡單說明NFA與DFA工作的區別: 比如有字串this is yansen’s blog,Regex為 /ya(msen|nsen|nsem)/ (不要在乎運算式怎麼樣,這裡只是為了說明引擎間的工作區別)。 NFA工作方式如下,先在字串中尋找 y 然後匹配其後是否為 a ,如果是 a 則繼續,尋找其後是否為 m 如果不是則匹配其後是否為 n (此時淘汰msen選擇支)。然後繼續看其後是否依次為 s,e,接著測試是否為 n ,是 n 則匹配成功,不是則測試是否為 m 。為什麼是 m 。因為 NFA 工作方式是以Regex為標準,反覆測試字串,這樣同樣一個字串有可能被反覆測試了很多次。 而DFA則不是如此,DFA會從 this 中 t 開始依次尋找 y,定位到 y ,已知其後為 a ,則查看錶達式是否有 a ,此處正好有 a 。然後字串 a 後為 n ,DFA依次測試運算式,此時 msen 不符合要求淘汰。nsen 和 nsem 符合要求,然後DFA依次檢查字串,檢測到sen 中的 n 時只有nsen 分支符合,則匹配成功。 由此可以看出來,兩種引擎的工作方式完全不同,一個(NFA)以運算式為主導,一個(DFA)以文本為主導。一般而論,DFA引擎則搜尋更快一些。但是NFA以運算式為主導,反而更容易操縱,因此一般程式員更偏愛NFA引擎。 兩種引擎各有所長,而真正的引用則取決與你的需要以及所使用的語言。
符號編輯 (摘自《 Regex之道》) Regex [2] 由一些普通 字元和一些 元字元(metacharacters)組成。一般字元包括大小寫字母和數字,而元字元則具有特殊的含義,我們下面會給予解釋。 在最簡單的情況下,一個Regex看上去就是一個普通的尋找串。例如,Regex"testing"中沒有包含任何元字元,它可以匹配"testing"和"testing123"等字串,但是不能匹配"Testing"。 要想真正的用好Regex,正確的理解元字元是最重要的事情。下表列出了所有的元字元和對它們的一個簡短的描述。
| 元字元 |
描述 |
| \ |
將下一個字元標記符、或一個反向參考、或一個八進位轉義符。例如,“\\n”匹配\n。“\n”匹配分行符號。序列“\\”匹配“\”而“\(”則匹配“(”。即相當於多種程式設計語言中都有的“逸出字元”的概念。 |
| ^ |
匹配輸入字串的開始位置。如果設定了RegExp對象的Multiline屬性,^也匹配“\n”或“\r”之後的位置。 |
| $ |
匹配輸入字串的結束位置。如果設定了RegExp對象的Multiline屬性,$也匹配“\n”或“\r”之前的位置。 |
| * |
匹配前面的子運算式任意次。例如,zo*能匹配“z”,“zo”以及“zoo”。*等價於{0,}。 |
| + |
匹配前面的子運算式一次或多次(大於等於1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等價於{1,}。 |
| ? |
匹配前面的子運算式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等價於{0,1}。 |
| {n} |
n是一個非負整數。匹配確定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的兩個o。 |
| {n,} |
n是一個非負整數。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等價於“o+”。“o{0,}”則等價於“o*”。 |
| {n,m} |
m和n均為非負整數,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”將匹配“fooooood”中的前三個o。“o{0,1}”等價於“o?”。請注意在逗號和兩個數之間不能有空格。 |
| ? |
當該字元緊跟在任何一個其他限制符(*,+,?,{n},{n,},{n,m})後面時,匹配模式是非貪婪的。非貪婪模式儘可能少的匹配所搜尋的字串,而預設的貪婪模式則儘可能多的匹配所搜尋的字串。例如,對於字串“oooo”,“o+?”將匹配單個“o”,而“o+”將匹配所有“o”。 |
| .點 |
匹配除“\r\n”之外的任何單個字元。要匹配包括“\r\n”在內的任何字元,請使用像“[\s\S]”的模式。 |
| (pattern) |
匹配pattern並擷取這一匹配。所擷取的匹配可以從產生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中則使用$0…$9屬性。要匹配圓括弧字元,請使用“\(”或“\)”。 |
| (?:pattern) |
非擷取匹配,匹配pattern但不擷取匹配結果,不進行儲存供以後使用。這在使用或字元“(|)”來組合一個模式的各個部分是很有用。例如“industr(?:y|ies)”就是一個比“industry|industries”更簡略的運算式。 |
| (?=pattern) |
非擷取匹配,正向肯定預查,在任何匹配pattern的字串開始處匹配尋找字串,該匹配不需要擷取供以後使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。預查不消耗字元,也就是說,在一個匹配發生後,在最後一次匹配之後立即開始下一次匹配的搜尋,而不是從包含預查的字元之後開始。 |
| (?!pattern) |
非擷取匹配,正向否定預查,在任何不匹配pattern的字串開始處匹配尋找字串,該匹配不需要擷取供以後使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。 |
| (?<=pattern) |
|