文章目錄
環視
(lookaround),分爲前瞻
(Lookahead )和後顧
(lookbehind),統稱爲零寬度斷言
(Lookahead and Lookbehind Zero-Length Assertions)。那什麼是零寬度斷言呢?正則環視實際上是有匹配字符的,但隨後又立即放棄了對字符的佔有或消耗。由於環視不消耗字符串
,只返回是否匹配(存在)的結果,所以我們稱爲零寬度斷言。前瞻(Lookahead )
肯定式前瞻(positive lookahead )
肯定式前瞻
是用來判斷一個字符串後接某種模式的字符串。語法爲:(?=子模式)
。是不是有點繞?下面我來舉例說明一下。
比如,我們只要
匹配後面緊接着ing的單詞,如下着色部分:
do
doing
looking
look
working
work
此時,就是肯定式前瞻
語法發揮作用的時候了。由於都是小寫字母,且只要ing前面的那部分,因此我們這麼寫就可以了[a-z]+(?=ing)
可以看到,環視(?=ing)
裏面的ing
不會被着色,也就是不會被佔用,這就是我們說的環視不會佔用(消耗)字符串,也就是所謂的零寬度
斷言。零寬度的意思即爲不消耗字符串
。
否定式前瞻(negative lookahead )
否定式前瞻
是用來判斷一個字符串不後接某種模式的字符串。語法爲:(?!子模式)
。現在我們要匹配的部分如下(匹配數字,但排除掉後接減號-的數字):
1+2+3-5-8
這個時候,我們只要這麼寫就可以了\d+(?!-)
後顧(Lookbehind )
肯定式後顧(positive lookbehind )
肯定式後顧
用來判斷一個字符串前面爲某種模式的字符串。語法爲:(?<=子模式)
。
下面舉例說明下,比如我們想要加號+後面的數字
11+22-33*444+5
這個時候,我們就需要用到肯定式後顧的語法,正則爲(?<=\+)\d+
,由於加號是元字符,表示量詞,所以我們要先轉義一下。
否定式後顧(negative lookbehind )
否定式後顧
是用來判斷一個字符串前面不爲某個模式的字符串。語法爲:(?<!子模式)
。
如下,我們只要着色部分的數據:
a1234
b1234
c1234
d1234
這個時候我們就要這麼寫(?<!c)1234
,如下圖:
環顧剖析
編程語言兼容性
- 前瞻語法,
大多數語言都支持
。如C#、java、javascript、php、python等等。 - 後顧語法,
大多數語言存在差異
:
C#
支持比較全面
;
java、python、php、perl等
在後顧環視裏面不能用*
、+
等不確定長度的量詞;
javascript
只有到了ES2018才支持
該語法,目前只有谷歌瀏覽器支持。
匹配細節
- 單純的環視語句,匹配到的其實是一個位置。如下面所示:
(?=\d)
匹配到的是數字前面(左邊)的那個位置
(?!\d)
匹配到的是非數字字符前面(左邊)的那個位置
(?<=\d)
匹配到的是數字字符後面(右邊)的那個位置
(?<!\d)
匹配到的是非數字字符後面(右邊)的那個位置
實例演練
校驗必須存在某些字符
密碼校驗,要求:由數字、字母組成,長度爲8~32之間,且必須同時包含數字、大小寫字母。
思路分析:
- 對於
校驗
這種類型的,我們首先要想到用來限制開頭^
和結尾$
的這兩個字符 - 對於校驗
必須同時存在某些字符
的問題,我們要想到用肯定式前瞻(?=子表達式)
的語法 - 公式歸納:
^(?=.*子表達式1)(?=.*子表達式2)(?=.*子表達式n).+$
,一個環視的子表達式用來保證必須含有某種模式的一種情況。此處環視的子表達式的出現順序無關,(?=.*子表達式1)(?=.*子表達式2)
和(?=.*子表達式2)(?=.*子表達式1)
這兩種順序是一樣的,因爲環視是零寬度斷言的,一個環視執行判斷完後,失敗則停止,成功則返回到原來開始環視的那個位置,如果有其他環視語句的話,則繼續執行下一個環視,以此類推,然後執行剩下的會消耗字符的主體匹配正則。一個環視相當於編程語言裏面的一個if判斷。但是值得注意的是,這些環視的子表達式必須放置開頭位置
,因爲正則是從左到右開始匹配的。
接下來,我們便來看看這個怎麼寫。
首先,數字可以寫成\d
或[0-9]
,這是等價的寫法,字母可以寫成[a-zA-Z]
,長度爲8~32,可以寫成{8,32}
,由此,我們首先可以得到如下正則
[0-9a-zA-Z]{8,32}
因爲是用來校驗的,所以我們在前後需要加上^
和$
,用來限制起始位置,可得
^[0-9a-zA-Z]{8,32}$
由於需要限制必須含有數字,因此要加上(?=.*[0-9])
,得
^(?=.*[0-9])[0-9a-zA-Z]{8,32}$
必須含有小寫字母,繼續加上(?=.*[a-z])
,得
^(?=.*[0-9])(?=.*[a-z])[0-9a-zA-Z]{8,32}$
必須含有大寫字母,繼續加上(?=.*[A-Z])
,得
^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,32}$
,這個就是對應於該題的完整的正則。
見過有些人會把環視寫在^
前面,如(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])^[0-9a-zA-Z]{8,32}$
對於校驗來講,這也是沒問題的。
但,千萬不要寫成這樣^[0-9a-zA-Z]{8,32}(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])$
,因爲這樣永遠也匹配不成功!
上面的正則圖形化後爲:
如上圖所示,由於前面的[0-9a-zA-Z]{8,32}
是會消耗(佔用)字符串的,又正則是從左到右匹配的,因此[0-9a-zA-Z]{8,32}
會先執行
,從左到右先消耗掉8到32個字符
,這個時候,後面的環視子表達式已經沒有可用的字符了(此時只剩下個結束位置
),所以會導致正則表達式匹配失敗!
下面我們來解釋一下肯定式環視後置的匹配:
^[0-9a-zA-Z]{8,32}(?=.*$)(?=.*$)(?=.*$)$
或^[0-9a-zA-Z]{8,32}(?=.*$)$
匹配如下,是可以成功的。
我們先來看一種只有環視(?=.*[0-9])
的情況,然後長度暫時改爲7,同時去掉限定結尾符號$
,如^[0-9a-zA-Z]{7}(?=.*[0-9])
,這個時候,是可以匹配到的,如下:
可以看到,此時匹配到了前面的7個字符,且第7個字符後面只要存在數字就會被匹配到。接下來,我們增加限制^[0-9a-zA-Z]{7}(?=.*[0-9])(?=.*[a-z])
,此時,第7個字符後面需要同時存在數字和小寫字母的纔會被匹配:
我們再增加限制,^[0-9a-zA-Z]{7}(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])
,此時,第7個字符之後,必須同時存在數字、大小寫字母纔會被匹配:
從上面的匹配可以得到結論
:當肯定式前瞻(?=)
放到一個會消耗字符串的正則後面,其實是需要等到該正則先匹配消耗字符,然後環視是從那個最後被消耗的字符的下一個位置開始從左到右環視的。
校驗必須不存在某些字符
驗證碼校驗,由數字和字母組成,長度爲4~6位,且不能存在數字0和字母oO。
思路分析:
- 對於校驗必須不存在某些字符的需求,可以用
否定式前瞻(?!子表達式)
的語法 - 公式歸納:
^(?!.*子表達式1)(?!.*子表達式2)(?!.*子表達式n).+$
首先,數字可以寫爲\d
或[\d]
或[0-9]
,字母爲[a-zA-Z]
,長度爲{4,6},得正則
^[0-9a-zA-Z]{4,6}$
,但不能存在數字0,得^(?!.*0)[0-9a-zA-Z]{4,6}$
,不能存在字母o和O,得
^(?!.*0)(?!.*[oO])[0-9a-zA-Z]{4,6}$
也可以寫成
^(?!.*[0oO])[0-9a-zA-Z]{4,6}$
匹配url的查詢條件的值
如下,我們需要寫一個正則,把着色部分的值取出來
https://www.baidu.com/s?id=test&name=bill&age=22
分析可得,每個值都是在=號的後面,且是以&分割。因此,我們可以利用肯定式後顧(?<=子表達式)
語法來匹配,即(?<==)[^&]+
校驗不以某種字符串結尾
如下,我們需要匹配以下被着色的數據,也就是要排除掉以gif和png結尾的數據。
123.jpg
123.gif
123.json
123.png
123.bmp
這個時候,我們可以考慮使用否定式後顧(?<!子表達式)
的語法,如^.+(?<!gif|png)$
微信公衆號: