雖然本系列文章開篇會簡單介紹正則表達式的一些基礎知識,但主要限於本系列文章所想強調的要點,所以本系列文章並不適合用於入門。php
若你是對正則表達式沒有任何概念的初學者,建議至少先閱讀網上備受推崇的《正則表達式30分鐘入門教程》,時間容許的話最好再閱讀《正則表達式必知必會》(才130多頁,寫得很是基礎,快的話一天可輕鬆讀完)。這樣在創建了對正則表達式的基本認識以後,再來閱讀本系列文章,才更爲合適。html
最後,文中如有錯漏,還請直接招呼板磚,不用客氣😊java
1、正則表達式構成python
1.正則表達式
正則表達式中的語法元素,從是否具備特殊含義的角度進行分類,可分爲下列兩大類、共五種語法元素:express
1)不具備特殊含義的語法元素編程
(1) 字面字符(文本字符):不具備特殊含義的單個字符,表明字符自身(即字符字面值);vim
(2) 普通轉義序列:由轉義前導符\後跟元字符所組成的字符序列,將具備特殊含義的元字符,轉義爲(即轉換爲)不具備特殊含義的字符自己(即字符字面值);api
2)具備特殊含義的語法元素ruby
(1) 元字符:具備特殊含義的單個字符,包括:\、(、)、[、]、{、}、.、-、*、+、?、|、^、$;
(2) 元轉義序列:由轉義前導符\後跟單個字符或多個字符組成,具備特殊含義,包括:\0octal-num、\num、\a、\A、\b、\b{}、\B、\B{}、\cX、\C、\d、\D、\e、\E、\f、\F、\g{}、\gnum、\G、\h、\H、\k{}、\k<>、\k''、\K、\l、\L、\n、\N、\N{}、\o{octal-num}、\pP、\p{}、\PP、\P{}、\Q、\r、\R、\s、\S、\t、\u、\U、\v、\V、\w、\W、\xhex-num、\x{hex-num}、\X、\z、\Z等;
(3) 特殊構造(特殊結構):由多個元字符和/或普通字符組成,具備特殊含義,包括:字符組[xyz]或[^xyz]、捕獲分組(sub-regex)、命名捕獲分組(?<name>sub-regex)、非捕獲分組(?:sub-regex)、預查分組(即環視分組)(?=sub-regex)或(?!sub-regex)或(?<=sub-regex)或(?<!sub-regex)、固化分組(即原子分組)(?>sub-regex)、嵌入條件分組(?(condition)true_sub-regex|false_sub-regex)、內聯修飾選項與取消內聯修飾選項分組(?modifier-modifier)、註釋分組(?#comment)、分支復位分組(?|sub-regex)、表達式引用分組(?R)或(?num)、平衡分組(?<-name>sub-regex)等。
(笨笨阿林原創文章,轉載請註明出處)
2.
從匹配的是位置仍是字符的角度來分類,可分爲以下四大類:
1)匹配字符的語法元素
(1) 字面字符(文本字符):表明字符自身(即字符字面值);
(2) 普通轉義序列:將具備特殊含義的元字符,轉義爲(即轉換爲)不具備特殊含義的字符自己(即字符字面值);
(3) 元字符:.;
(4) 下面這些元轉義序列:
固定字符:\a、\b(字符組內部)、\e、\f、\n、\r、\t、\v(非Perl系);
字符組簡記:\d、\D、\h、\H、\N{}、\p{}與\pP、\P{}與\PP、\s、\S、\v(僅Perl系)、\V、\w、\W
進制轉義字符:\octal-num(Perl系中也可寫做\o{octal-num})、\xhex-num(Perl系中也可寫做\x{hex-num})、\uhex-num(非Perl系,Ruby1.9+等個別語言中還可寫做\u{hex-num});
控制字符:\cX系列;
其餘:\C、\N、\R、\X。
2)匹配位置的語法元素
(1) 下面這些元字符:
^、$
(2) 下面這些元轉義序列:
錨點:\A、\z、\Z、\b(字符組外部)、\b{}、\B、\B{}、\G;
其餘:\<、\>。
(3) 預查分組:
(?=sub-regex)、(?!sub-regex)、(?<=sub-regex)、(?<!sub-regex)。
3)既可能匹配字符,也可能匹配位置的語法元素
(1) 由下限次數爲0的量詞所限定的子表達式,下限次數爲0的量詞包括:?、*、{0,}、{0,m}、{,m}(逗號「,」前面爲空的這種寫法僅部分正則引擎支持,不推薦這種寫法);
(2) 下面這些元轉義序列:
引用:\num、\g{num}、\gnum、\k{name}、\k<name>、\k'name'(若是引用的是文本,則匹配字符,若是引用的是位置或空字符串,則匹配的是位置);
(3) 特殊構造(特殊結構):捕獲分組(sub-regex)、命名捕獲分組(?<name>sub-regex)、非捕獲分組(?:sub-regex)、固化分組(即原子分組)(?>sub-regex)、嵌入條件分組(?(condition)true_sub-regex|false_sub-regex)等,當這些分組中的sub-regex爲空時,匹配的是位置;不爲空時,若sub-regex匹配字符,則這些分組匹配的是字符,不然匹配的是位置。
4)既不匹配字符,也不匹配位置的語法元素
除上述語法元素以外的其餘語法元素,這包括:\K、內聯修飾選項與取消內聯修飾選項分組(?modifier-modifier)、註釋分組(?#comment)等。
3.
注意,語法元素有時也會稱之爲子表達式;固然,子表達式的概念要比語法元素更爲豐富,涵蓋面更廣。所以,需根據上下文予以準確理解。
2、字符串構成
1.
從正則表達式的角度來看,字符串一般由位置和字符所共同構成,但空字符串僅由單個位置構成(該位置既是空字符串的起始位置,也是空字符串的結束位置,可同時匹配表示字符串起始位置的元字符^和表示字符串結束位置的元字符$)。
對於字符串「Regex」而言,是由五個字符以及六個位置構成的,理解這一點對於正則表達式的匹配原理的理解很重要。
2.
字符串中的位置,其實也是組成該字符串的字符的索引,所以,位置0就是用來索引(即定位)字符R的索引0。字符串「Regex」始於索引0(即位置0)處,止於索引5(即位置5)處。
當正則引擎在字符串中查找匹配時,能夠認爲在字符串中有一個匹配定位指針,該指針能夠在字符串中的各個位置之間移動(通常是從左到右依次移動,但回溯時也會從右向左移動;另外,.Net中還支持從右向左匹配)。
3.
查找匹配過程當中,下一次匹配的起始位置與前一次匹配的結束位置每每是相同的:
正則式:/regex/
字符串:regexregex
找到第一個子字符串"regex",開始於位置0結束於位置5
找到第二個子字符串"regex",開始於位置5結束於位置10
(笨笨阿林原創文章,轉載請註明出處)
3、匹配過程與匹配定位指針、匹配控制權
1.
匹配過程從字符串的角度來看的話,必然老是從字符串中的一個位置開始匹配的,多是從字符串的起始位置匹配,也多是從字符串中間的某兩個字符之間的位置開始匹配,甚至多是從字符串的結束位置開始匹配(.Net中支持從右向左匹配)。固然,絕大部分狀況下,均是從字符串的起始位置開始匹配的。
當在某個位置嘗試匹配失敗,正則引擎將移動字符串中的匹配定位指針到字符串中的下一個位置開始繼續嘗試匹配。這樣逐個位置嘗試,直到得到匹配,或者一直到字符串結尾仍未得到匹配則報告匹配失敗。
2.
匹配過程從正則表達式的角度來看的話,必然老是從正則表達式的起始位置從左至右逐個語法元素開始嘗試匹配的(但多選分支結構中的狀況稍微複雜些:傳統型NFA正則引擎因爲遵循「最左先到先得原則」,一旦其中某個分支得到了匹配,將不會繼續嘗試匹配剩下的分支;而DFA和POSIX NFA正則引擎因爲遵循「最左最長原則」,必須選擇各個分支中所得到的最長匹配,所以會逐個分支嘗試匹配)。
正則表達式中的某個語法元素一旦在字符串中得到了匹配(若該語法元素後面有量詞限定的話,需知足其重複次數,且有可能存在回溯,詳見後文解釋),則表示該語法元素成功得到了匹配,因而匹配控制權轉移到下一個語法元素,重複該過程。
原則上,匹配控制權一旦從某個語法元素轉移出去,則該語法元素不能再次從新得到。不過,懶惰量詞造成的回溯例外(懶惰量詞所限定的語法元素一旦得到了該量詞的下限次匹配以後,會先將匹配控制權轉移給緊隨其後的語法元素,若緊隨其後的語法元素沒法匹配,則會將匹配控制權返回給該語法元素)。(若是暫時看不明白不要緊,後文都會有詳細解釋)。
若正則表達式中的某個必須匹配的語法元素(而由下限次數爲0的量詞所限定的語法元素則爲可選匹配)一旦在字符串中沒法得到匹配,則該正則表達式匹配失敗。
4、佔有字符(消費字符與消耗字符)匹配和不佔有字符(零寬度)匹配
1.
正則表達式匹配過程當中,若其中的某個語法元素匹配到的是字符,而非位置,而且在字符串中移動了匹配定位指針,此時可分爲兩種狀況:
1) 所匹配的字符被保存到了最終的匹配結果中(即返回了所匹配到的字符),那麼就認爲該子表達式消費了這些字符;
2) 所匹配的字符未被保存到最終的匹配結果中(即沒返回所匹配到的字符),那麼就認爲該子表達式消耗了這些字符(好比位於元轉義序列\K以前的子表達式)。
不管是消費了字符,仍是消耗了字符,均屬於佔有了字符。
若是該子表達式匹配的僅僅是位置,或者雖然匹配了字符,但最終並不實際移動字符串中的匹配定位指針(好比預查分組),那麼就認爲這個語法元素是不佔有字符的,即屬於零寬度的。
佔有字符仍是不佔有字符,最終以是否實際移動了字符串中的匹配定位指針爲準。
2.
佔有字符是互斥的,不佔有字符是非互斥的。
所謂互斥指的是一個字符,只能由一個語法元素匹配,一旦被某個語法元素匹配後佔有了,則不能爲其餘語法元素所匹配佔有;所謂非互斥指的是一個位置,能夠同時由多個不佔有字符(即零寬度)的語法元素匹配。
(笨笨阿林原創文章,轉載請註明出處)
5、八大原則簡介
1.
受《精通正則表達式》一書中「最左原則」、「最長原則」以及衍生的「最左最長原則」的啓發,在此基礎上我進一步推廣擴展,總結爲八大原則。其中包括六大基本原則與兩大衍生原則,先簡要介紹以下(後文結合語法元素會有詳細解釋):
六大基本原則:
1) 最左原則:在一個字符串中,若一個正則表達式可能有多個匹配結果時,其中最靠近字符串左邊的起始位置的那個匹配結果老是會優先於其餘的匹配結果被返回;
2) 最長原則(即長度優先原則):若是在字符串中的某個位置存在多個可能的匹配,將返回最長文本(即最多字符)的那個匹配;
3) 先到先得原則(即順序優先原則):在同一個位置上,若是有多個長度不一樣的匹配結果,將返回最早得到匹配的結果,或先後兩個由貪婪量詞或懶惰量詞所限定的子表達式發生匹配衝突時,後者僅得到其下限次數的匹配,而前者將得到超過其上限次數的儘量多的匹配;
4) 逐位置依次嘗試匹配原則:匹配老是從字符串的起始位置(即位置0)開始,從左到右地逐個位置嘗試匹配整個正則表達式;
5) 總體匹配優先原則:整個正則表達式得到匹配的優先級要高於貪婪量詞所限定的子表達式;
6) 佔有匹配優先原則:整個正則表達式得到匹配的優先級要低於佔有量詞所限定的子表達式。
兩大衍生原則:
1) 最左最長原則:非全局模式下,若是在字符串中的多個位置中的每一個位置均有多個可能的匹配文本,DFA和POSIX NFA引擎會優先選擇最靠左邊位置的全部可能的匹配文本當中最長的文本;
2) 最左先到先得原則:非全局模式下,若是在字符串中的多個位置中的每一個位置均有多個可能的匹配文本,傳統型NFA引擎會優先選擇最靠左邊位置的全部可能的匹配文本當中最早得到匹配的文本。
2.
這些原則看似平淡無奇,但正如「兩點間直線距離最短」這樣顯而易見的幾何學公理,倒是支撐起整個宏偉的歐幾里得幾何學的基石同樣,這八大原則也是正則引擎匹配機制的基礎,理解它們是理解正則引擎匹配行爲的關鍵。
(笨笨阿林原創文章,轉載請註明出處)
參考資料:
一)官方文檔
Perl:
Perl regular expressions (perlre)(英文)
Perl Regular Expressions Reference (perlreref)(英文)
Perl Regular Expression Backslash Sequences and Escapes (perlrebackslash)(英文)
Perl Regular Expression Character Classes (perlrecharclass)(英文)
PCRE:
PHP:
.Net(C#、VB):
Java:
Regular Expressions Tutorials(英文)
JavaScript:
EMCAScript:RegExp (Regular Expression) Objects(英文)
Python2.7:
Regular expression operations(英文)
Python3.4:
Regular expression operations(英文)
Ruby:
Vim:
模式及查找命令 For Vim version 7.4(中文)
Search commands and patterns For Vim version 7.3(英文)
GNU Grep:
GNU Sed:
GNU awk:
二)書籍
《精通正則表達式》英文版及中文版 做者:Jeffrey E·F·Friedl 譯者:餘晟 電子工業出版社 2012-07
《正則指引》做者:餘晟 電子工業出版社 2012-05
《正則表達式必知必會》做者:Ben Forta譯者:楊濤 人民郵電出版社2015-01
《冒號課堂:編程範式與OOP思想》做者:鄭暉 電子工業出版社 2009-10
三)其餘
本系列文章還參考了網上的大量資料,除了少部分資料因爲未做大量修改(但基本上也有少許修改,由於網上文章隨意性較大,不少明顯的筆誤或先後矛盾之處,如若不改反而讓人迷糊)而標明瞭原做者和出處以外,其他因爲基本上已按本身的理解做了大量改寫,所以沒有再一一予以說明,在此對原文做者表示歉意並感謝。
另外,文中圖片小部分來自網絡,大部分爲本人制做,也再也不一一說明,在此對原圖做者表示歉意並感謝。
(未完待續)
【預告:本《刨根究底正則表達式》系列的下一篇將正式開始逐個介紹各正則表達式語法元素;而《刨根究底字符編碼》系列的下一篇將重點介紹UTF-16編碼,敬請關注!】