正則表達式

1:什麼是正則表達式?
正則表達式是對字符串操作的一種邏輯公式。完整的正則表達式由兩種字符構成:元字符(metacharacters,提供強大的描述能力)和文字(literal)。
基本元字符    
.  匹配任意單個字符
|  邏輯或操作符
() 限定多選結構,分組, 捕獲文本
[]  定義一個字符集合,匹配該集合中的一個字符
[^]  對字符集合求非(是對整個集合求非,而不是緊挨着^符號的字符)
-  在於符集合中定義一個區間。如[A-Za-z],但是如果是在[]外面或者[-abc]就不表示元字符。 
\  對下一個字符轉義。比如/n表示換行。

匹配位置元字符
^ 匹配一行開頭
$ 匹配一行結尾

數量元字符
*  匹配前一個字符(子表達式)零次或多次
*?  *的懶惰型版本(防止正則表達式的“貪婪性”)
+  匹配前一個字符或子表達式一次或多次
+?  +的懶惰型版本
?  匹配前一個字符或子表達式零次或一次
{n}  匹配前一個字符或子表達式的n次重複,比如[A-Z]{6}表示匹配由六個大寫字母組成的字符串。
{m,n}  匹配至少m次至多n次
{m,}  匹配至少m次
{m,}?  {m,}的懶惰型版本
 
附一張表格列出基本的正則元字符在常用工具中的表示法。
 
PCRE記法 vi/vim grep awk sed
* * * * *
+ \+ \+ + \+
? \= \? ? \?
{m,n} \{m,n} \{m,n\} {m,n} \{m,n\}
\b * \< \> \< \> \< \> \y \< \>
(…|…) \(…\|…\) \(…\|…\) (…|…) (…|…)
(…) \(…\) \(…\) (…) (…)
\1 \2 \1 \2 \1 \2 不支持 \1 \2
附POSIX字符組
POSIX字符組 說明 ASCII語言環境 Unicode語言環境
[:alnum:]* 字母字符和數字字符 [a-zA-Z0-9] [\p{L&}\p{Nd}]
[:alpha:] 字母 [a-zA-Z] \p{L&}
[:ascii:] ASCII字符 [\x00-\x7F] \p{InBasicLatin}
[:blank:] 空格字符和製表符 [ \t] [\p{Zs}\t]
[:cntrl:] 控制字符 [\x00-\x1F\x7F] \p{Cc}
[:digit:] 數字字符 [0-9] \p{Nd}
[:graph:] 空白字符之外的字符 [\x21-\x7E] [^\p{Z}\p{C}]
[:lower:] 小寫字母字符 [a-z] \p{Ll}
[:print:] 類似[:graph:],但包括空白字符 [\x20-\x7E] \P{C}
[:punct:] 標點符號 [][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] [\p{P}\p{S}]
[:space:] 空白字符 [ \t\r\n\v\f] [\p{Z}\t\r\n\v\f]
[:upper:] 大寫字母字符 [A-Z] \p{Lu}
[:word:]* 字母字符 [A-Za-z0-9_] [\p{L}\p{N}\p{Pc}]
[:xdigit:] 十六進制字符 [A-Fa-f0-9] [A-Fa-f0-9]
2:正則表達式匹配過程。
正則表達式兩條普遍適用的原則
1,優先選擇左端的匹配結果。
2,標準的匹配量詞是優先匹配的。(如*,+,?,{m,n})
規則一對於egrep這類只關心是否匹配而不關心位置的這類程序來說是無關緊要的,但是對於查找和替換這個一定要小心:如
[root@localhost ~]# cat angus
angus blog http://angus717.blog.51cto.com/
angus gus
blog
http://angus.blog.51cto.com/index.html
如果需要匹配gus ,此時第二行會優先匹配angus,這時你如用替換命令,就會和你想象中的不一樣了
 
[root@localhost ~]# sed 's/gus/nus/' angus    
annus blog http://angus717.blog.51cto.com/
annus gus
blog    
http://annus.blog.51cto.com/index.html
對於第二條,還有個過度匹配優先。
現在把angus文件修改下,用http:\/\/.*index.html來匹配http://angus.blog.51cto.com/index.html
 
[root@localhost ~]# cat angus
angus blog http://angus717.blog.51cto.com/    
angus gus    
blog    
http://angus.blog.51cto.com/index.html Regular Expressions
這個匹配會首先匹配http://然後.*會把整行匹配完,一直到Expressions這地方。但是index.html必須匹配,所以.*匹配的內容必須從後向前交出一下字符來匹配l,從末尾的s開始直到Regular的l時候這時匹配l了,接着繼續匹配m,很明顯u不能匹配l,繼續向前匹配l,直到匹配index.html。
 
3:正則表達式引擎:
什麼是正則表達式的引擎,對於上個例子說http:\/\/.*index.html這個就是引擎。
正則表達式引擎一般都是有文字字符,量詞,字符組,括號等按照一定方式組合起來的。
正則引擎大體上可分爲不同的兩類:DFA和NFA,而NFA又基本上可以分爲傳統型NFA和POSIX NFA。
DFA Deterministic finite automaton 確定型有窮自動機
NFA Non-deterministic finite automaton 非確定型有窮自動機
DFA引擎因爲不需要回溯,所以匹配快速,但不支持捕獲組,所以也就不支持反向引用和$number這種引用方式,目前使用DFA引擎的語言和工具主要有awk、egrep 和 lex。
POSIX NFA主要指符合POSIX標準的NFA引擎,它的特點主要是提供longest-leftmost匹配,也就是在找到最左側最長匹配之前,它將繼續回溯。同DFA一樣,非貪婪模式或者說忽略優先量詞對於POSIX NFA同樣是沒有意義的。
大多數語言和工具使用的是傳統型的NFA引擎,它有一些DFA不支持的特性:
 
捕獲組、反向引用和$number引用方式;

環視(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫做預搜索;

忽略優化量詞(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫做非貪婪模式;

佔有優先量詞(?+、*+、++、{m,n}+、{m,}+,目前僅Java和PCRE支持),固化分組(?>…)
 
PCRE和POSIX
常見的正則表達式記法,其實都源於Perl,實際上,正則表達式從Perl衍生出一個顯赫的流派,叫做PCRE(Perl Compatible Regular Expression),『\d』、『\w』、『\s』之類的記法,就是這個流派的特徵
 
POSIX的全稱是Portable Operating System Interface for uniX,它由一系列規範構成,定義了UNIX操作系統應當支持的功能,所以“POSIX規範的正則表達式”其實只是“關於正則表達式的POSIX規範”,它定義了BRE(Basic Regular Expression,基本型正則表達式)和ERE(Extended Regular Express,擴展型正則表達式)兩大流派。在兼容POSIX的UNIX系統上,grep和egrep之類的工具都遵循POSIX規範,一些數據庫系統中的正則表達式也符合POSIX規範。
 
DFA 和 NFA 反映了將正則表達式在應用算法上的根本區別。NFA爲:"表達式主導(regex-directed表達式的控制權在不同元素間切換)引擎";DFA爲:"文本主導(text-directed)引擎"

NFA
實質上,在表達式主導匹配過程中,每一個子表達式都是獨立的。這不同於反向引用,子表達式之間不存在內在聯繫,而只是整個正則表達式的各個部分。與NFA不同,DFA引擎在掃描字符串時,會記錄“當前有效”的所以匹配可能。舉個例子說明下,這個例子本身沒有實在意義。
用 some[then|where|body]來匹配這句話“there are something to do by somebody. ”
NFA的處理過程是在字符中先找到s,標記爲備用位置,然後先處理最先出現s的something,檢查是否匹配o,接着匹配m,接着匹配e,此時是檢查是否匹配t,從上面例子看出something匹配字符t和字符h,到字符e的時候,很明顯i不能匹配,所以NFA引擎的回溯功能會回到some的e字符處開始匹配where,很明顯匹配失敗,然後繼續返回匹配body,也是失敗,此時something匹配不成功,繼續向後匹配,同樣的過程匹配somebody成功。
對應DFA的處理過程,它想從there開始匹配s,定位到s,檢查後面是否有o,m,e。然而e後面是t,所以只匹配then中的t(where和body)已經匹配失敗,接着匹配h,接着匹配i,很顯然then也匹配失敗,繼續向後匹配,然後匹配到somebody。
 
關於回溯借用http://su1216.iteye.com/blog/1662046的這個例子看起來更明白點。
3,固化分組
固化分組的格式(?>...)
對於“(?>...)”中的內容部分(省略號省略的部分)來說,與之前的匹配規則一致,沒有什麼區別,但是,當此部分表達式匹配完畢,開始匹配括號外面的部分時,括號內的所有備用狀態都會被放棄,也就是說,在固化分組匹配結束時,它已經匹配的文本已經固化爲一個單元,只能作爲整體而保留或放棄。括號內的子表達式中未嘗試過的備用狀態都不復存在了,所以回溯永遠也不能選擇其中的狀態(至少是,當此結構匹配完成時,“鎖定(locked in)”在其中的狀態)
固化分組到底有什麼用處呢?
 原來格式爲123.456,後來因爲浮點數顯示問題,部分數據格式變爲123.456000000789這種,,要求做到只保留小數點後面2-3位,但是,最後一位不能爲0,這個正則如何寫呢?(下面直接考慮小數點後面的數字),寫出正則之後,我們還要用這個正則去匹配數據,把原來的數據替換成匹配的結果。
我們可以立刻寫出這樣的正則【\.\d\d[1-9]?\d*】,PHP代碼爲
$str = preg_replace('\.(\d\d[1-9]?)\d*','\\1',$str);    
很明顯,這種寫法,對於部分數據格式爲123.456的這種格式,白白的處理了一遍,爲了提高效率,我們還要對這個正則進行處理。從123.456這個字符串跟其他的比較一下,我們發現,是疑問123.456這個數據後面沒數字了,所以,白白處理一遍。那好辦,我們對這個正則改造一下,把後面的量詞*改成+,這樣對於123.45 小數點後面1,2位數字的,不會去白白處理,而且,對三位以上數字的,處理正常。其PHP代碼爲
$str = preg_replace('\.(\d\d[1-9]?)\d+','\\1',$str);
對於上面匹配過程,首先(小數點前123不說了),【\.】匹配".",匹配成功,把控制權給下一個【\d】,【\d】匹配“4”成功,把控制權給第二個【\d】,這個【\d】匹配“5”成功,然後,把控制權給了【[1-9]?】,由於量詞是【?】,正則表達式遵循“量詞優先匹配”,而且,此處是【?】,還會留下一個回溯點。然後匹配"6"成功,然後把控制權給【\d+】,【\d+】發現後面沒字符了,最遵循“後進先出”規則,回到上一個回溯點,進行匹配,這時,【[1-9]?】會交還出其匹配的字符“6”,【[1-9]?】匹配“6”成功。匹配完成了。結果發現【(\d\d[1-9]?)】匹配的結果確是"45",並不是我們想要的“456”,“6”被【\d+】匹配去了。
 
能否讓【[1-9]?】匹配一旦成功,不進行回溯呢?這就用到了我們上面說的"固化分組", PHP(preg_replace函數)中使用的正則引擎支持固化分組,我們根據固化分組的寫法,可以把代碼改成如下方式
$str = preg_replace('\.(\d\d(?>[1-9]?))\d+','\\1',$str);
【\.】匹配".",匹配成功,把控制權給下一個【\d】,【\d】匹配“4”成功,把控制權給第二個【\d】,這個【\d】匹配“5”成功,然後,把控制權給了【(?>[1-9]?)】,由於,正則表達式遵循“量詞優先匹配”,所以【[1-9]?】匹配6,由於此時是固化分組匹配到的6,所以,此處是【?】,不會留下一個回溯點。然後把控制權給【\d+】,【\d+】發現後面沒字符了,前面又沒有回溯點,所以整體匹配也就視頻,"0.625"不需要處理這正是我們需要的。(精通正則表達式p170頁)。
 
4,環視
環視格式
(?=exp)                 匹配exp前面的位置
(?<=exp)               匹配exp後面的位置
(?!exp)                  匹配後面跟的不是exp的位置
(?<!exp)                匹配前面不是exp的位置
 
示例:目標:將字符串中 1234567 這樣的數字變成這種形式: 1,234,567  (精通正則表達式p59頁)
需要處理的字符串The US population is 298444215,處理的php代碼
$str = 'The US population is 298444215'
$regex = '/(?=(\d\d\d)+)/';    
echo preg_replace($regex, ',', $str); //輸出:The US population is ,2,9,8,4,4,4,215    
因爲我們沒有加一個結尾限定符,因此從 215 往前的每一位數字後面都有 3個數字,於是在前面的每一位就都加了一個逗號。
 
第一次修該
$regex = '/(?=(\d\d\d)+$)/';    //=就是加了一個 $ ,匹配字符串結尾    
echo preg_replace($regex, ',', $str); //輸出:The US population is ,298,444,215
很明顯這個也不符合我們要求,逗號前必須有一位數字
 
第二次修改
$regex = '/(?<=\d)(?=(\d\d\d)+$)/';    
echo preg_replace($regex, ',', $str); //輸出:The US population is 298,444,215
看似很正確的樣子
$str=將字符串中 1234567 這樣的數字變成這種形式。
$regex = '/(?<=\d)(?=(\d\d\d)+$)/';    
echo preg_replace($regex, ',', $str); //輸出:將字符串中 1234567 這樣的數字變成這種形式。
顯然這個不符合我們要求,我們只需要找到一個位置,此位置的後面不是一個數字就可以了
$regex = '/(?<=\d)(?=(\d\d\d)+(?!\d))/';
echo preg_replace($regex, ',', $str); //輸出:將字符串中 1,234,567 這樣的數字變成這種形式。
 
 
 繼續整理中!!!!
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章