正則表達式(四):正則表達式的與或非

來源: infoq  發布時間: 2011-04-06 11:23  閱讀: 21561 次  推薦: 2   原文鏈接   [收藏]  

  我們使用正則表達式,熟練掌握各種功能和結構只是手段,解決實際的問題才是真正的目的。要解決真正的問題,就必須有解決問題的思路,正則表達式的功能,說到底,可以歸納為三種邏輯,為了表述方便,我們分別稱為與、或、非。

邏輯關系

說明

在某個位置,某些元素(字符、字符組或者子表達式)必須出現

在某個位置,某個元素或許不出現,或許不出現,或許長度不固定;要出現的,是某幾個元素中的一個

在某個位置,某些元素不能出現

  一般來說,正則表達式千變萬化,總是這三種邏輯的組合。比如匹配雙引號字符串:

  "quoted string"

邏輯關系

分析

首尾的雙引號字符必須出現

兩個雙引號之間的字符個數是不確定的(如果是空字符串””,則兩個雙引號之間沒有字符)

兩個雙引號之間不能出現雙引號字符

  再比如匹配html中的open-tag(比如<h1>)和close-tag(比如</h1>):

邏輯關系

分析

首尾必須分別是<和>,如果是close-tag,則<之后必須出現/

<和>之間必須出現至少一個字符(<>不是一個合法的tag)

<之后不能是/字符,如果是open-tag,<之后不能出現/

  下面我們來講解三種邏輯的對策。

  與

  “與”是正則表達式中最普通的邏輯關系。一般來說,如果正則表達式中的元素沒有任何量詞(quantifier,比如*、?、+)修飾,就是“與”關系。比如『<』,就表示“這里必須出現<字符”;『cat』,就表示“這里必須依次出現c、a、t,3個字符”。

  不過“與”的情況并沒有這么簡單,有時候,“必須出現”的是若干個元素,或者說,幾個元素必須同時出現,但它們之間并不相連,這是非常容易犯錯的時候,不過現在我們不舉具體的例子,稍晚一點再說。

  或

  “或”是正則表達式中最靈活的邏輯關系。正則表達式能應對各種不同的文本,“或”功能不可或缺。

  如果“或”的意思是,元素可以出現,也可以不出現,或者出現的次數不確定,可以用量詞來表示“或”關系。比如表達式『a?』,表示在此處,字符a可以出現,也可以不出現;表達式『(ab)+』,表示在此處,字符串ab必然要出現1次,也可以出現無限多次。

  如果“或”的意思是,可以出現的是某幾個元素中的一個,則應該使用字符組或者多選結構。當元素都是單個字符時,就應該使用字符組『[…]』:比如匹配單詞cat或者cut,除去開頭的a、結尾的t是固定的,之中“或許出現a,或許出現u”,所以應當使用字符組『[au]』,整個正則表達式就是『c[au]t』。當元素不只單個字符(只要有一個元素不只單個字符)時,就應該使用多選結構『(…|…)』:比如不但要匹配單詞cat或者cut,還要匹配單詞chart、conduct和court,出去開頭的a、結尾的t是固定的,之中“或許出現a,或許出現u,或許出現har,或許出現onduc,或許出現our”,這時候就應該使用多選結構『(a|u|har|onduc|our)』,整個正則表達式就是『c(a|u|har|onduc|our)t』。

  當然,多選分支也可以表示字符組,比如『[au]』就可以表示為『(a|u)』,兩者的功能是完全等價的,但是字符組的效率更高,也更直觀(畢竟,大家都習慣了簡單的『[au]』,而看到『(a|u)』則多半要想一想。

  在實踐中,“與”和“或”經常同時出現,而且關系不那么簡單,下面舉一個例子說明:為了隱藏真實的結構,我們需要用URL進行偽裝,比如這個URL pattern:/foo/bar_tmp.php。

  在真正的系統里,foo是模塊名,bar是控制器名,tmp是方法名。合法的URL并不要求3個名字每次都出現,可以只出現控制器名(/foo),也可以只出現控制器名和模塊名(/foo/bar.php),也可以3者都出現(/foo/bar_tmp.php)。

  這里的模塊名、控制器名、方法名,都可以用『[a-z]+』匹配,這里為說明方便,我們暫且用foo、bar、tmp代替對應的表達式。初看起來,這個表達式只包含“與”和“或”兩種關系:

邏輯關系

分析

/foo必須出現

/bar、_tmp、.php都是可選出現的

  所以,正則表達式是『/foo(/bar)?(_tmp)?(\.php)?』。

  這個表達式確實可以匹配/foo、/foo/bar.php和/foo/bar_tmp.php,但是,它也可以匹配/foo_tmp、/foo/bar_tmp等形式,雖然這些形式并不是合法的。

  仔細研究之后,我們發現,“與”和“或”的關系并沒有那么簡單,而應該是這樣的:

邏輯關系

分析

/foo必須出現

/bar和.php是可選出現的,但必須同時出現,或同時不出現(與)

在/bar和.php都出現的前提下,_tmp才可以出現(或)

  /foo必須出現,這很好表示,暫且不去管它;/bar和.php如果出現,必須同時出現,所以它們應該作為一個元素,寫作『(/bar.php)』;整個元素可選出現,所以給它添加量詞,得到『(/bar.php)?』;最后,在/bar和.php都出現的前提下,_tmp才可以出現,所以將『(_tmp)?』填充到『(/bar.php)?』,得到『(/bar(_tmp)?.php)?』,最后加上開頭的/foo,整個表達式就是『(/bar(_tmp)?.php)?』。到此,整個關系終于完整表達出來,表達式也不會發生錯誤匹配。

  非

  “非”是正則表達式中最難處理的邏輯關系。因為沒有直接對應的結構,“非”的處理比較吃力。

  最簡單的“非”,意思是此處不能出現某個字符,這一點通常很直觀,似乎用排除型字符組『[^…]』就可以解決。比如雙引號字符串的匹配,首尾兩個雙引號很容易匹配,其中的內容肯定不是雙引號(暫時不考慮轉義的情況),所以可以用『[^"]』表示即可,其長度不確定,所以用*來限定,所以整個表達式就是『"[^"]*"』,非常簡單。

  但是,事情果真都如此簡單嗎?我們仍然舉cat和cut的例子,如果仍然希望匹配c開頭、t結尾的單詞,但不希望匹配cut,可以寫成『c[^u]t』,是否就可以了?

  這個表達式的意思是:最開頭的字母是c,之后是一個不為u的字符,之后是t。沒錯,它確實不會匹配cut,也可以匹配cat。但是,chart、conduct、court等等,它也沒法匹配,因為[^u]的意思是:匹配一個不是u的字符。

  那么,把『[^u]』改成『[^u]+』好了,這樣應該就可以解決問題了。但是真的如此嗎?『[^u]+』的意思是,一個或若干(最多到無窮)個字符,但每一個字符都不能是u。所以,盡管『c[^u]+t』能匹配cat和chart,卻不能匹配conduct和court。

  看來,“非”真是比較難對付,讓人非常糾結。好在,也不是沒有辦法解決它。回復到與-或-非的觀點,分析要實現的功能:

邏輯關系

分析

以c開頭,以t結尾

c和t之間可以出現的字母必須多于一個,沒有上限

c和t之間不能只有一個字符u

  如果只考慮“與”和“或”兩個邏輯,表達式很好寫,是『c[a-z]+t』,再把剩下的條件附加上去,就可以解決問題了。我們仔細看“非”的條件:c和t之間不能只有一個字符u。既然『[^u]+』表達的并不是這個意思,我們不妨換一種表述法:在c之間的位置向后看,不能出現cut。這一點,正好對應否定順序環視(positive look-ahead)功能,『(?!cut)』就是用來進行這種判斷的,它判斷之后的字符串能不能由cut匹配,但并不真正真正進行匹配,也不會移動“當前位置”。所以我們將它放在表達式的最開頭,得到『(?!cut)c[a-z]+t』。這個表達式的邏輯是:只有在當前位置右側字符串不能由cut匹配的情況下,才從這里開始,向右嘗試用c[a-z]+t。

  如果我們更進一步,需要排除掉cat和cut,可以把否定順序環視改為『(?!c[au]t)』。這樣就能保證,匹配到的肯定不是cat或者cut。

  更復雜一點,如果我們要驗證這樣一個字符串:它全部由小寫字母構成,長度不超過12位,其中不能包含unfavored或者unwanted。也可以照章處理,先匹配“長度不超過12位”的小寫字母『[a-z]{,12}』,然后寫出匹配“不需要匹配內容”的正則表達式,『(unfavored|unwanted)』,再用否定順序環視將它“排除”即可,只是這次要注意,不能直接寫『(?!(unfavored|unwanted))』,因為它只能排除『(unfavored|unwanted)』出現在字符串開頭的情況,為了排除它出現在字符串中的情況,我們要把否定順序環視改為『(?![a-z]*(unfavored|unwanted))』,這樣就確保完整的“排除”,整個表達式就是『(?![a-z]*(unfavored|unwanted))[a-z]{,12}』。

  總結一下,正則表達式中的“非”,除去能用排除型字符組直接表示的,復雜一點的“非”邏輯都是按照這樣的思路進行的:先用一個正則表達式準確匹配需要“排除”的字符串,再用環視功能排除掉它——“非”確實是正則表達式中,最難處理的邏輯關系,好在它并不復雜,而且,除去一些比較古老的工具(比如Apache 1.3),現在各種工具和語言,基本都支持這種功能。

2
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()