本節內容
- 正則表達式簡介
- 正則表達式中的字符
- 元字符詳解
- 常用正則表達式實例
- 正則表達式的匹配過程
- 正則表達式中的標志位-flag
- 參考資料
需要提前說明的是: 正則表達式的語法是由正則表達式引擎決定的(目前主流的正則引擎分為3類:DFA、傳統型NFA 和 POSIX NFA),不同編程語言或應用程序所使用的引擎可能不同,它們對正則表達式的語法支持會有差別。
一、正則表達式簡介
1. 什么是正則表達式
正則表達式(Regluar Expressions)又稱規則表達式,這個概念最初是由Unix中的工具軟件(如sed 和 grep)普及開的。正則表達式在代碼中常簡寫為REs,regexes或regexp(regex patterns)。它本質上是一個小巧的、高度專用的編程語言。 許多程序設計語言都支持通過正則表達式進行字符串操作。例如,在Perl中就內建了一個功能強大的正則表達式引擎。
2. 正則表達式能做什么
正則表達式的主要應用對象是文本,使用正則表達式可以指定想要匹配的字符串規則,然后通過這個規則來匹配、查找、替換或切割那些符合指定規則的文本。總體來講,正則表達式可以對指定的文本實現以下功能:
- 匹配驗證: 判斷給定的字符串是否符合正則表達式所指定的過濾規則,從而可以判斷某個字符串的內容是否符合特定的規則(如email地址、手機號碼等);當正則表達式用于匹配驗證時,通常需要在正則表達式字符串的首部和尾部加上^和$,以匹配整個待驗證的字符串。
- 查找與替換: 判斷給定字符串中是否包含滿足正則表達式所指定的匹配規則的子串,如查找一段文本中的所包含的IP地址。另外,還可以對查找到的子串進行內容替換。
- 字符串分割與子串截取: 基于子串查找功能還可以以符合正則表達式所指定的匹配規則的字符串作為分隔符對給定的字符串進行分割。
二、正則表達式中的字符
正則表達式的主要應用對象是文本,其最基礎的功能是文本匹配,而文本是由一個個的字符組成,因此正則表達式實際上是對字符的匹配。正則表達式中的字符分為 普通字符 和 元字符,而正則表達式就是這些普通字符和特殊元字符組合成的表示一個特定匹配規則的表達式。
1. 普通字符
實際上,大多數字符都將簡單地匹配它們的自身值,它們被稱為普通字符,如數字(0-9),字母(a-z, A-Z)等。例如,正則表達式hello123
將匹配字符串'hello123',因為該這則表達式中都是普通字符,不包含特殊元字符。當然,我們可以通過指定正則表達式的匹配模式為 忽略字母大小寫模式,這么正則表達式hello123
將能夠匹配'Hello123', 'HellO123', 'HELLO123'等字符串。
提示: 其實我們并不需要去記憶哪些字符是普通字符,我們只需要知道哪些字符是特殊元字符就可以了,除了特殊元字符之外的所有字符都是普通字符。
2. 元字符
上面提到,正則表達式除了進行字符自身之的匹配外,還可以基于指定的規則進行模糊匹配。這就意味著它需要一些特殊字符來表示這些模糊的匹配規則,因此這些特殊字符默認情況下并不能匹配到它們自身的字面值,而是表示某些特殊的功能。這些特殊元字符包括:., [, ], (, ), *, +, ?, ^, $, \, |。這些特殊字符的使用,會在下面進行詳細講解。正則表達式的重點和難點也就在于對正則表達式引擎的工作原理以及對這些特殊元字符掌握和靈活運用。
提示: 那么如果想匹配這些特殊元字符本身的字面值怎么辦呢?我們可以通過其中一個特殊字符對其它特殊字符進行轉義,從而達到可以匹配這些特殊字符自身字面值的目的。
三、元字符詳解
現在我們來詳細說明一下正則表達式中的特殊元字符到底能完成哪些復雜的匹配功能。
1.單個字符匹配
說明: 所有的特殊字符在[ ]內都將失去其原有的特殊含義:
- 有些特殊字符在[ ]中被賦予新的特殊含義,如 '^'出現在[ ]中的開始位置表示取反,它出現在[]中的其他位置表示其本身(變成了一個普通字符);
- 有些特殊字符則變為普通字符,如 '.', '*', '+', '?', '$'
- 有的普通字符變為特殊字符,如 '-' 在[ ]中的位置不是第一個字符則表示一個數字或字母區間,如果在[ ]中的位置是第一個字符則表示其本身(一個普通字符)
- 在[ ]中,如果要使用'-', '^' 或']',可在在它們前面加上反斜杠,或把'-', ']'放在第一個字符的 位置,把'^'放在非第一個字符的位置。
2. 預定義字符集
我們可以在反斜杠后面跟上一個指定的字母來表示預定義的字符集合
3. 字符次數匹配--量詞
在正則表達式中,我們還可以指定匹配某個字符出現次數
說明: {m,n}中的m和n可以省略其中一個,{,n}相當于{0,n},{m,}相當于{m,整數最大值}。
我們可以得出以下結論:
- {0,1}或{,1} 等價于 ?
- {1,} 等價于 +
- {0,} 等價于 *
我們優先選擇使用 ?, + 和 *,因為他們書寫簡單,也可以使整個正則表達式變得簡潔。
說明: ? 這個字符在正則表達中與 ?, +, *, {m,n}連用時還有一個額外的功能,就是將匹配模式由貪婪模式(盡可能的增加匹配次數) 變成 非貪婪模式(盡可能減少匹配次數), 這個會在下面的內容中進行詳細說明。
4. 邊界匹配
正則表達式中還可以對邊界位置進行匹配,如一個字符串的開頭或結尾,一個單詞的開頭或結尾。
5. 邏輯與分組
語法 | 說明 | 表達式實例 | 可匹配到的字符串實例
6. 特殊構造
說明: 上面所說的“不消耗字符串內容”是指只是進行匹配,但是不移動原始字符串的匹配位置,這樣就可以完成多次匹配。下面有個匹配密碼的正則表達式實例,就是用這個特性巧妙完成的。
四、常用正則表達式實例
通常寫一個合適的正則表達式是比較耗費時間的,因此我們可以保留一些常用的正則表達式以備不時之需。但是需要說明的是,沒有任何一個人敢說自己寫的正則表達式是百分之百嚴謹的,而且也沒有百分之百相同的匹配需求,因此這里只是列舉我自己寫的幾個常用的正則表達式,歡迎大家留言討論。
說明: 下面只是一些簡單的匹配規則,實際情況中需要我們根據具體情況再這些正則表達式的首部和尾部加上相應的邊界符,如:^, $, \A, \Z, \b, \B等
匹配一個網絡地址(URL)
[a-zA-Z]+://[\S]+
需要說明的是,網絡地址不一定是一個網頁地址(http或https鏈接),還可能是ftp地址等。如果我們要匹配特定協議的網絡地址,如http或http鏈接可以這樣寫:
(https?://)?[\S]+
匹配一個IP地址
最簡單的寫法:
(\d+[.]){3}\d+
嚴謹一點的寫法:
(((?:[1-9]\d?)|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))[.]){3}((?:[1-9]\d?)|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))
或
((([1-9]\d?)|(1\d{2})|(2[0-4]\d)|(25[0-5]))[.]){3}(([1-9]\d?)|(1\d{2})|(2[0-4]\d)|(25[0-5]))
匹配一個郵箱地址
最簡單的寫法:
\S+@\S+\.\S+
嚴謹一點的寫法(保證只出現一個@符):
[^\s@]+@[^\s@]+\.[^\s@]+
如果要非常嚴謹的話,就要區分不同的郵箱了,因為網易(126郵箱,163郵箱)、qq郵箱、hotmail郵箱以及gmail郵箱對郵箱名稱中可以包含的字符都有不同的要求。
匹配網易郵箱:6-18個字符,只能包含字母、數字和下劃線,且只能以字母開頭
[a-zA-Z]\w{5,17}@(126|163)\.com
匹配qq郵箱:3-18個字符,只能包含字母、數字、點、減號和下劃線
[\w.-]{3,18}@qq\.com
如果要多個郵箱的嚴謹匹配用一個正則表達式來匹配,比如要匹配網易郵箱和qq郵箱可以這樣寫:
(?:[a-zA-Z]\w{5,17}@(126|163)\.com)|(?:[\w.-]{3,18}@qq\.com)
當然,也可以分別匹配多個正則表達式,再通過程序邏輯來得到最后的結果
匹配密碼是否合法:
要求比較簡單的情況,比如只要求為非空字符且限定密碼長度為6-18位
^\S[6-18]$
要求比較復雜的情況,比如必須同時包含含數字、大小字母、小寫字母和標點符號,這就需要用到前面所說的正則表達式的特殊構造了(?=...), (?!=...)
(?=^.{6,8}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W+)
如果要求必須同時包含且只能包含數字、大小字母、小寫字母和標點符號,可以這樣寫:
(?=^[\d\Wa-zA-Z]{6,8}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W+)
匹配大陸身份證號碼(15位或18位)
\d{15}|(\d{18}|(\d{17}[Xx]))
小貼士: 當今的身份證號碼有15位和18位之分。1985年我國實行居民身份證制度,當時簽發的身份證號碼是15位的,1999年簽發的身份證由于年份的擴展(由兩位變為四位)和末尾加了效驗碼,就成了18位。這兩種身份證號碼將在相當長的一段時期內共存。兩種身份證號碼的含義如下:
匹配日期(年-月-日)
(\d{2}|\d{4})-((0?[1-9])|(1[0-2]))-((0?[1-9])|([12][0-9])|(3[01]))
24小時制時間(小時:分鐘:秒)
(((0?|1)[0-9])|(2[0-3])):([0-5][0-9]):([0-5][0-9])
其他常用正則表達式
匹配內容 | 正則表達式 |
---|---|
QQ號碼 | [1-9]\d{4,} |
中國大陸固定電話號碼 | (\d{3,4}-)?\d{7,8} |
中國大陸手機號碼 | 1\d{10} |
中國大陸郵政編碼 | \d{6} |
漢字 | [\u4e00-\u9fa5] |
中文及全角標點符號 | [\u3000-\u301e\ufe10-\ufe19\ufe30-\ufe44\ufe50-\ufe6b\uff01-\uffee] |
不含abc的單詞 | (?=\w+)(?!abc) |
正整數 | [1-9]+ |
負整數 | -[1-9]+ |
非負整數(正整數+0) | [1-9]+ |
非正整數(負整數+0) | -[1-9]+ |
整數+0 | -?[1-9]+ |
正浮點數 | \d+.\d+ |
負浮點數 | -\d+.\d+ |
浮點數 | -?\d+.\d+ |
再次說明,實際情況中需要我們根據具體情況再這些正則表達式的首部和尾部加上相應的邊界符,如:^, $, \A, \Z, \b, \B等
五、正則表達式的匹配過程
基于量詞(如?, +, *, {m,n}, {m,})的字符重復次數匹配是正則表達式優于普通字符串處理方法的一個重要方面,也是正則表達式的一個重要組成部分。量詞對正則表達式的匹配過程具有非常重大的影響,因此在介紹正則表達式的匹配過程時,必不可少的要提到量詞的兩個重要分類:
- 匹配優先量詞 我們上面介紹的量詞就是匹配優先量詞包括:?, +, *, {m,n},但是不包括{m}
- 忽略優先量詞 在匹配優先量詞后面加上一個問號就變成了忽略優先量詞,包括:??, +?, *?, {m,n}?
如果大家對這兩個詞不熟悉的話,那么大家一定聽說過這兩個詞:
- 貪婪模式(或非惰性匹配) 顧名思義,就是在整個表達式匹配成功的前提下,盡可能多的去匹配量詞所修飾的字符
- 非貪婪模式(或惰性匹配) 在整個表達式匹配成功的前提下,盡可能少的去匹配量詞所修飾的字符
它們之間的關系是:
- 匹配優先量詞修飾的子表達式使用的是就是貪婪模式(非惰性匹配);
- 忽略優先量詞修飾的子表達式使用的就是模式就是非貪婪模式(惰性匹配);
下面我們通過一個實例來分析 貪婪模式 和 非貪婪模式 下的正則匹配過程:
要匹配的字符串:'abcbd'
貪婪模式正則表達式:
a[bcd]*b
非貪婪模式正則表達式:
a[bcd]*?b
1. 貪婪模式匹配過程分析
2. 非貪婪模式匹配過程分析
3. 總結
貪婪模式與非貪婪模式影響的是被量詞修飾的子表達式的匹配行為,貪婪模式在整個表達式匹配成功的前提下,盡可能多的匹配;非貪婪模式在整個表達式匹配成功的前提下,盡可能少的匹配。另外,非貪婪模式只被部分NFA引擎所支持。從匹配效率上來看,能達到相同匹配結果時,貪婪模式的匹配效率通常會比較高,因為它回溯過程會比較少。
4. 補充示例
偶然看到一個比較好的關于貪婪模式的匹配過程示例,分享給大家。該示例出自 《這篇文章》
首先由“<”取得控制權,由位置0位開始嘗試匹配,匹配字符“a”,匹配失敗,第一輪匹配結束。第二輪匹配從位置1開始嘗試匹配,同樣匹配失敗。第三輪從位置3開始嘗試匹配,匹配字符“<”,匹配成功,控制權交給“d”。
“d”嘗試匹配字符“d”,匹配成功,控制權交給“i”。重復以上過程,直到由“>”匹配到字符“>”,控制權交給“.*”。
“.*”屬于貪婪模式,將從B處后的字符“t”開始,一直匹配到E處,也就是字符串結束位置,將控制權交給“<”。
“<”從字符串結束位置嘗試匹配,匹配失敗,向前查找可供回溯的狀態,把控制權交給“.”,由“.”讓出一個字符“c”,把控制權再交給“<”,嘗試匹配,匹配失敗,向前查找可供回溯的狀態。一直重復以上過程,直到“.*”讓出已匹配的字符“<”,實際上也就是到讓出了已匹配的子串
“</div>cc"
為止,“<”才匹配字符“<”成功,控制權交給“/”。接下來由“/”、“d”、“i”、“v”分別匹配對應的字符成功,此時整個正則表達式匹配完畢。
六、正則表達式中的標志位-flag
上面提到的貪婪模式與非貪婪模式影響的是被量詞修飾的子表達式的匹配行為,而這里所說的標志位將會影響正則表達式的整體工作方式。不同編程語言中通常都會有預設的常量值來表示這些標志位,大家在用到時自己查下文檔既可以。常用的標志位如下:
標志位作用 | 描述 |
---|---|
表示忽略大小寫的標志位 | 默認情況下,正則表達式在進行匹配時是區分大小寫的 |
表示匹配任何字符的標志位 | 這個標志位影響的是'.'這個元字符,因為它默認情況下是匹配除換行符之外的任意字符,當指定這個標志位之后,'.'將可以匹配任意字符 |
表示多行匹配的標志位 | 它影響是是'^'和'$'這兩個元字符,它們默認匹配的是一個字符串的開頭和結尾,指定這個標志位后,它們可以匹配每一行的行首和行尾 |
七、參考資料
- https://docs.python.org/3.5/howto/regex.html
- http://blog.csdn.net/lxcnn/article/details/4756030
- 一張經典的總結圖(收藏了很久,忘記了出處,如果哪位知道請告知,這里會附上鏈接地址,謝謝。)
文章列表