正則表達式實現密碼檢查


起因

起因是一個朋友問怎么實現一個密碼檢查功能:

  1. 密碼只能由大寫字母,小寫字母,數字構成;
  2. 密碼不能以數字開頭;
  3. 密碼中至少出現大寫字母,小寫字母和數字這三種字符類型中的兩種;
  4. 密碼長度8-100位

然后他貼了寫的代碼:

$value = 'A1234567890a';
$rule = '/^[A-Z][A-Za-z]{7,100}|^[A-Z][A-Z0-9]{7,100}|^[a-z][A-Za-z]{7,100}|^[a-z][0-9a-z]{7,100}$/';
var_dump(preg_match($rule,(string)$value));    

一看這變量名以$開頭,大概是php的,但我不怎么懂PHP,只看得懂中間的正則,猜最后一行是輸出匹配結果。

這個正則顯然是不滿足上面說的條件的,|的優先級比$低,所以前面的表達式沒有控制密碼長度的作用。我依稀記得曾經學正則也看過相關的題目,這種復雜的正則檢查是要用零寬斷言。

然而我覺得零寬斷言這東西寫起來復雜,別人看更覺得復雜的東西,團隊里面顯然不是所有人都會去弄懂的。寫這種復雜正則,以后需求變更,修改起來簡直是噩夢,所以沒怎么認真看,只知道有這么個東西,於是我就認真翻了一下相關的文章。

鏈接如下:
https://deerchao.net/tutorials/regex/regex.htm

這個是我覺得寫得比較好的,內容很充實的一個文章了。

解決問題

對於這個需求,我們先完成第124點,這三個比較簡單,正則寫出來如下

var regex = /^[A-Za-z][A-Za-z0-9]{7,99}$/

這個問題難寫的就是第3點,如果不用零寬斷言,是要用很多個|去做判斷,非常長。

第三點是

密碼中至少出現大寫字母,小寫字母和數字這三種字符類型中的兩種;

換個說法就是

不能全是大寫字母,不能全是小寫字母,不能全是數字。

或者說

存在一個非大寫字母,存在一個非小寫字母,存在一個非數字。

第一種

對於第一種說法,可以用

var regex = /^(?![A-Z]*$)/; // 不能全是大寫字母
var regex2 = /^(?![a-z]*$)/; // 不能全是小寫字母
var regex3 = /^(?![0-9]*$)/; // 不能全是數字

這里的*可以換成+,只會在空字符串的時候有差異,對於我們上面的需求是沒有差異的,因為限定了8-100的長度。

與前面的正則組合起來就是

var regex = /^(?![A-Z]*$)(?![a-z]*$)(?![0-9]*$)[A-Za-z][A-Za-z0-9]{7,99}$/;

第二種

對於第二種說法,可以用

var regex = /^(?=.*[^A-Z])/ // 存在一個非大寫字母
var regex2 = /^(?=.*[^a-z])/ // 存在一個非小寫字母
var regex3 = /^(?=.*[^0-9])/ // 存在一個非數字

這里*不能替換成+,為什么可以自己思考一下,我會給一反例證明替換有問題。

組合起來就是

var regex = /^(?=.*[^A-Z])(?=.*[^a-z])(?=.*[^0-9])[A-Za-z][A-Za-z0-9]{7,99}$/;

如果將所有的*替換成+,那么對於Aaaaaaaa,正則匹配會失敗,根據需求應該是成功。

第三種

什么?居然還有第三種?是的,當然還有第三種,和第四種。

剛剛我們寫的斷言,按照鏈接給的名字是零寬度正預測先行斷言(?=exp)零寬度負預測先行斷言(?!exp),當然名字其實不重要的,翻譯過來的名字有很多種,不同的文章也不同,也不知道哪個是比較官方的。

所以我們可以用另外兩種斷言實現,分辨是零寬度正回顧后發斷言(?<=exp)零寬度負回顧后發斷言(?<!exp)

對於第三種和第四種,它是從后面檢查前面的斷言,這種斷言據說js是不支持的,但是chrome的引擎似乎是支持的,也不清楚是怎么回事。

(?<!exp)形式寫第一種說法

var regex = /(?<!^[A-Z]*)$/; // 不能全是大寫字母
var regex2 = /(?<!^[a-z]*)$/; // 不能全是小寫字母
var regex3 = /(?<!^[0-9]*)$/; // 不能全是數字

同樣的*可以替換成+,組合起來就是

var regex = /^[A-Za-z][A-Za-z0-9]{7,99}(?<!^[A-Z]*)(?<!^[a-z]*)(?<!^[0-9]*)$/

第四種

不廢話了直接貼代碼

var regex = /(?<=[^A-Z].*)$/ // 存在一個非大寫字母
var regex2 = /(?<=[^a-z].*)$/ // 存在一個非小寫字母
var regex3 = /(?<=[^0-9].*)$/ // 存在一個非數字

組合后如下,同樣的不能把*替換成+,反例是aaaaaaaA

var regex = /^[A-Za-z][A-Za-z0-9]{7,99}(?<=[^A-Z].*)(?<=[^a-z].*)(?<=[^0-9].*)$/;

第五種、第六種……

組合有很多種,因為條件都說的很清楚了,四種斷言是可以組合的,所以其實遠不止以上說的四種情況,重要的是要掌握四種斷言的本質。

結尾

本文只是一個引子,並不是說怎么學正則,學習正則可以參考上面給的鏈接,這里再給一次https://deerchao.net/tutorials/regex/regex.htm

警告:不要寫過於復雜的正則,多人協作中,代碼的可讀性遠比性能和炫技重要。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM