正規表示式--regular expression
說到正規表示式,很多前端小夥伴們是望而卻步或者淺嘗而止,感覺其難以理解。其實正規表示式並沒有那麼複雜,只要你清晰地知道你想要解決的問題並學會使用正規表示式,那麼你就可以輕易地解決這些問題。
正規表示式線上理解工具
用途
正規表示式(簡稱regex)是一種工具和其他工具一樣,他是為了解決某一類專門的問題發明的。
例如:
- 你正在搜尋一個檔案中的size單詞或者想要替換所有的
size
單詞,但是不想要替換包括size
的單詞比如fontsize
這樣的就不替換。 - 你想要檢查一個表單中郵箱或者手機號是否是正確的語法格式。
- 你只想搜尋某個檔案裡特定位置的某個單詞,比如行首或者行尾。
這些問題其實是我們寫程式時候經常能遇到的,我們也可以通過條件處理和字串操作來解決它們,但是你的解決方案會十分的複雜。
但是,這些問題我們都可以使用一些精心構造的正規表示式語句來解決。
什麼是正規表示式
簡單來說正規表示式是一些用來匹配和處理文字的字串。它是文字處理方面功能最強大的工具之一。正規表示式語言用來構造正規表示式,正規表示式用來完成搜尋和替換的操作。
以下的例子都是合法的正規表示式:
.ber
.
www\.tyweb\.top
[a-zA-z0-9_.]*
<[Hh]1>.*</[Hh]1>
\r\n\r\n
\d{3,3}-\d{3,3}-\d{4,4}
匹配單個字串
1. 匹配純文字
- 文字 -
hello world!hello web!
- 正規表示式 -
hello
- 結果 -
hello
world!hello
web!
這裡使用正規表示式的是純文字,它將匹配原始文字里面的所有 hello
。
字母大小寫的問題:正規表示式是區分大小的,所以hello
不匹配Hello
,不過絕大數的正規表示式的實現也支援不區分大小寫的匹配操作,比如JavaScript
中可以使用i
標誌來強制執行一次不區分字母大小寫的搜尋。
2. 匹配任意字串
- 文字 -
web1
web2
ty
wee
wee
web3df
1web2
- 正規表示式 -
web.
- 結果 -
web1
web2
ty
wee
weeweb3
df
1web2
在正規表示式中特殊自負床用來給出要搜尋的內容,.
可以匹配任何一個單個的字元。當然,在同一個正規表示式中允許出現多個 .
,並且它既可以連續出現,也可以間隔這出現在模式的不同位置。
3. 匹配特殊字串
剛剛我們有說到 .
的用法,並且一些特殊字元有一些特殊的用法,那麼問題來了,如果我們在需要匹配的字串中有這些字串,而我們有想要匹配出來怎麼辦呢。
我們需要想辦法高速正規表示式你需要的是 .
本身而不是他在正規表示式中的特殊含義。所以我們在它前面加上一個 \
(反斜槓)來對它進行轉義。
- 文字 -
web1
2web2
ty
wee
web3df
1web.2
- 正規表示式 -
.web\.
- 結果 -
web1
2web2
ty
wee
web3df1web.
2
匹配一組字串
1. 匹配多個字串中的一個和利用字元區間
上面我們有說過如何匹配單個字串,但是如果現在有一個檔案列表。我們只想找出.是包含我們想要字母的檔案怎麼辦?看例子:
- 文字 -
aweb.html
cweb.html
ty.html
wee.html
web3.html
1web.html
- 正規表示式 -
[ac]web\.html
- 結果 -
aweb.html
cweb.html
ty.html
wee.html
web3.html
1web.html
可以看到我們這裡使用 [ac]
作為開頭,這個集合將只匹配字元 a
或 c
。我們可以多處使用[]
來使得我們的表示式更加靈活,當然我們是需要根據需要來做的。
當然我們如果想要匹配a到z的話當然不可能是寫那麼多字母模式[a~z]
是完全等價的,相同道理的還有[0~9]
。
2. 取非匹配
字符集合通常用來指定一組必須匹配其中之一的字元。但是某些場合,我們需要反過來做,給出一組不需要得到的字元,換句話說,除了那個字符集合裡的字元,其他字元都可以匹配。
- 文字 -
aweb.html
cweb.html
ty.html
wee.html
web3.html
1web.html
- 正規表示式 -
[^0~9]web\.html
- 結果 -
aweb.html
cweb.html
ty.html
wee.html
web3.html
1web.html
3. 使用元字串
元字串是一些在正規表示式裡有特殊含義的字元,類似 .
, ]
所以這些字元就無法用來表示自身,當然之前我們也說過我們在元字串的前面加上一個反斜槓\
來進行轉義,讓其匹配自身。
比如下面這個例子我們想匹配 arr[0]
。
- 程式碼 -
var arr = new Array();
console.log(arr[0].length);
- 正規表示式 -
arr\[0\]
當然這個例子有點小題大做,我們平時情況下遇到同時匹配arr[1]
arr[2]
arr[3]
等情況才會用到正則。
4. 匹配空白字元
元字元 | 說明 |
---|---|
[b] | 回退(並刪除)一個字元(Backspace鍵) |
f | 換頁符 |
n | 換行符 |
r | 回車符 |
t | 制符表(Tab按鍵) |
v | 垂直製表符 |
一般情況來說我們匹配 \r
\n
\t
的情況稍微多見一些。
5. 匹配特定的字元類別
元字元 | 說明 |
---|---|
d | 任何一個數字字元(等價於 [0~9] ) |
D | 任何一個非數字字元(等價於 [^0~9] ) |
w | 任何一個字母數字字元(大小寫均可)或下劃線字元 (等價於[a-zA-Z0-9_] ) |
W | 任何一個非字母數字字元或非下劃線字元(等價於[^a-zA-Z0-9_] ) |
s | 任何一個空白字元(等價於[\f\n\r\t\v] ) |
S | 任何一個非空白字元(等價於[^\f\n\r\t\v] ) |
6. 匹配十六或者八進位制數值
這裡就不詳細介紹,我們需要知道的hi正則能做到這一點類似匹配十六進位制 \x0A
其實和等價於 \n
,匹配八進位制 \011
等價於 \t
。
7. 使用POSIX字元
首先:JavaScrip是不支援在正規表示式中使用POSIX字元的。
稍作了解,POSIX
字元類是一種簡寫的形式
字元類 | 說明 |
---|---|
[:alnum:] |
任何一個字母或者數字(等價於 [a-zA-Z0-9] ) |
[:alpha:] |
任何一個字母(等價於 [a-zA-Z] ) |
[:blank:] |
空格或者製表符 (等價於 [\t ] ) |
[:cntrl:] |
ASCII控制字元(ASCII0到31加上127) |
[:digit:] |
任何一個數字(等價於 [0-9] ) |
[:graph:] |
和 [:print:] 一樣但是不包括空格 |
[:lower:] |
任何一個小寫字母(等價於 [a-z] ) |
[:print:] |
任何一個可列印字元 |
[:punct:] |
不屬於 [:alnum:] 和 [:cntrl:] 的任何一個字元 |
[:space:] |
任何一個空白字元(等價於 [^\f\n\r\t\v ] ) |
[:upper:] |
任何一個小寫字母(等價於 [A-Z] ) |
[:xdigit:] |
任何一個十六進位制數字(等價於 [a-fA-F0-9] ) |
重複匹配
從之前所瞭解的匹配規則中我們學會了使用各種元字元,字符集,字元類去匹配單個字元。但是當我們想要匹配出一個類似 loulan@qq.com
這樣的郵箱呢?
1. 匹配一個或者多個字元
- 文字 -
hello everyone, you can email me to loulan@qq.com or loulou@qq.com.
- 正規表示式 -
\w+@\w+\.\w+
- 結果 -
hello everyone, you can email me to loulan@qq.com or loulou@qq.com.
想要匹配同一個字元或者字符集的多次重複(不包括0次),只要簡單的給這個字元或者字符集加上一個 +
字元作為字尾即可。比如 [0-9]
匹配一個數字, [0-9]+
匹配一個或者多個連續的數字。
需要注意的是給字符集加上+
字尾的時候必須放在字符集[]
的外面。不然就是匹配或者+
的單個字元了。
其實我們剛剛的這個表示式如果遇到 ty.top.@qq.com
就會出現問題。仔細的你會在下面找到答案。
2. 匹配零個或者多個字元
先看一個例項,這段的匹配出現了小問題。 .loulan@qq.com
帶上了我們不需要的 .
。
- 文字 -
hello everyone, you can email me to ty.top@qq.com or loulou@qq.com.
- 正規表示式 -
[\w.]+@[\w.]+\.\w+
- 結果 -
hello everyone, you can email me to ty.top@qq.com or .loulou@qq.com.
其實這種匹配模式需要使用*
來進行匹配他的用法和+
一樣,但是它能匹配該字元或者字符集零次或者多次出現的情況。
- 文字 -
hello everyone, you can email me to ty.top@qq.com or loulou@qq.com.
- 正規表示式 -
\w+[\w.]*@[\w.]+\.\w+
- 結果 -
hello everyone, you can email me to ty.top@qq.com
or .loulou@qq.com
.
可以稍微理解下這個表示式每個字元所代表的意義,以及為什麼能解決我們的問題。
3. 匹配零個或者一個字元
有了一個多個,零個多個,怎麼會少了零個或者一個字元的匹配呢。用法和+ *
一樣,只不過規則不一樣了。我們看下區別:
- 文字 -
http://www.baidu.com/
https://www.baidu.com/
- 正規表示式 -
http://[\w./]+
- 結果 -
http://www.baidu.com/
https://www.baidu.com/
- 文字 -
http://www.baidu.com/
https://www.baidu.com/
- 正規表示式 -
https?://[\w./]+
- 結果 -
有些同學會發現其實在字符集中有些元字元並沒有進行轉義,但是匹配還是成功了。一般來說在字符集中間的的元字元會被解釋成普通字元,不需要轉義,當然,轉義了的話也沒有任何壞處,也還是能匹配成功的。
4. 防止過度匹配
我們看一個問題:
- 文字 -
<i>left</i> middle <i>right</i>
- 正規表示式 -
<[iI]>.*</[iI]>
- 結果 -
<i>left</i> middle <i>right</i>
我們分析一下這個問題,其實是因為.
其實匹配了中間部分的所有字元而沒有適可而止將他們分開進行匹配。那麼我們怎麼讓它適可而止呢?
- 文字 -
<i>left</i> middle <i>right</i>
- 正規表示式 -
<[iI]>.*?</[iI]>
- 結果 -
<i>left</i> middle <i>right</i>
很簡單的我們在 *
後面加了 ?
就可以讓它不再貪婪,我們把加上 ?
的 *
叫做他的惰性版本。
其實剛剛說的這些都是正則匹配裡的數字元量符號,一樣的使用方法有以下數字元量符:
數字元量符 | 說明 |
---|---|
* | 匹配前一個字元或者字符集的零次或者多次出現 |
+ | 匹配前一個字元或者字符集的一次或者多次出現 |
? | 匹配前一個字元或者字符集的一次或者零次出現 |
{n} | 匹配前一個字元或者字符集的n次重複 |
{m,n} | 匹配前一個字元或者字符集的至少m次至多n次的重複 |
{n, } | 匹配一個字元或者字符集的n次或者更多次重複 |
*? |
* 的惰性版本 |
+? |
+ 的惰性版本 |
{n, }? |
{n, } 的惰性版本 |
位置匹配
我們遇到這麼一個問題,我們只想匹配 arry
這個單個單詞,但是它匹配到了我們不想要的內容:
- 文字 -
arry in tyarryIt.com
- 正規表示式 -
arry
- 結果 -
arry in tyarryIt.com
1. 單詞邊界
我們需要一個規則來限制邊界,\b可以做到
,他限制了一個單詞的開始或者結尾
- 文字 -
arry in tyarryIt.com
- 正規表示式 -
\barry\b
- 結果 -
arry in tyarryIt.com
這裡我們只匹配 arry
本身,所以我們在前後都加入 \b
。當然我們可以只在開頭或者結尾使用來找到以我們想要單詞作為開頭或者結尾的單詞。
需要注意的是\b
只匹配位置不匹配字元。有些正則還支援另外兩種元字元\<
\>
只匹配單詞的開頭和結束,不過支援他們的正規表示式引擎並不多。
與之相反\B
表明不匹配一個單詞邊界。
2. 字串邊界
用來定義字串邊界的元字元有兩個,一個是用來定義字串開頭的 ^
另一個是匹配字串結尾的 $
。其使用方法是和單詞邊界一致的。
位置元字元 | 說明 |
---|---|
^ |
匹配字串的開頭 |
\A |
匹配字串的開頭 |
$ |
匹配字串的結束 |
\Z |
匹配字串的結束 |
\< |
匹配單詞開頭 |
\> |
匹配單詞結束 |
\b |
匹配單詞邊界 |
\B |
和\b 相反 |
使用子表示式
子表示式
讓我們思考一下下面這個正則:
- 文字 -
arry innbsp;nbsp;tyarryIt.com
- 正規表示式 -
nbsp;{2, }
從表示式中我們能知道寫這個表示式的本意是想匹配連續的 nbsp;
。但是這個表示式並不能達到我們預期的結果,因為 {2, }
只會匹配其前面緊挨的字元。所以只能匹配nbsp;;
這樣的字串,但是無法匹配 nbsp;nbsp;
這樣的字串。
上面這個表示式就引出了子表示式的概念,子表示式是一個更強大的表示式的一部分,把表示式劃分成一系列的表示式是為了把那些子表示式當作一個獨立的元素來使用。子表示式必須使用 ()
來括起來。
這樣再讓我們完美解決上面的需求:
- 文字 -
arry innbsp;nbsp;tyarryIt.com
- 正規表示式 -
(nbsp;){2, }
這樣的話 (nbsp;)
是一個表示式。它將被視為一個獨立的元素,而他後面的 {2, }
將作用域這個子表示式。
子表示式的巢狀
子表示式允許巢狀,並且允許多重的巢狀,這種巢狀,在層次上理論沒有限制,但是在我們的工作中當然是需要適可而止的。太多的巢狀會讓匹配模式變得難以閱讀和理解。
回溯引用-前後一致匹配
前後一致的問題讓我們不禁想到HTML的標籤:
- 文字 -
<body>
<h1>it is h1</h1>
<h2>it is h2</h2>
<h3>it is h3</h3>
<h4>it is h4</h5>
</body>
- 正規表示式 -
<[hH][1-6]>.*?</[hH[1-6]]>
這可能是我們在想到匹配標籤時候心裡的第一想法。
但是,細心的同學會發現我們最後的一個標籤 <h4>***</h5>
因為手誤寫錯了。但是思考一下我們發現,這種錯誤的地方也會被成功匹配。那我們怎樣才能前後一致進行匹配呢?
回溯引用匹配
下面這個表示式把字串中重複的幾個單詞匹配出來了。
- 文字 -
Try you you best and and you will win win the game.
- 正規表示式 -
[ ]+(\w+)[ ]+\1
- 結果 -
Try you you best and and you will win win the game.
我們來分析一下上面的表示式,[ ]+
匹配一個或者多個空格,\w+
匹配一個或者多個字母或者數字,隨後,[ ]+
匹配一個或者多個空格,而 (\w+)
又是一個子表示式,當然這裡子表示式不是用來做重複匹配的,這裡只是把這部分表示式單獨劃分出來以便在後面進行引用。最後一部分 \1
這就是一個回溯引用,而它引用的是前面劃分出來的那個子表示式,他的意思是當前面的 \w+
匹配到 you
的時候他也匹配 you
,前面的\w+
匹配到 and
的時候他也匹配 and
。當然如果有多個子表示式 \1 \2 \3
分別代表模式裡的第一,第二,第三個表示式,我們可以把回溯引用想象成一個變數的重複呼叫。
前後查詢:
往前查詢
- 文字 -
https://www.tyweb.top/
http://www.tyweb.top/
ftp://ftp.tyweb.top/
- 正規表示式 -
.+(?=:)
- 結果 -
https://www.tyweb.top/
http://www.tyweb.top/
ftp://ftp.tyweb.top/
在上面的正規表示式中 .+
匹配任意文字,子表示式 ?=:
匹配 :
,但是需要注意的是 :
並沒有出現在最終的結果中,我們用?=:
表明的是,只要找到 :
就可以了,不要包括在最終的匹配結果裡。用術語來說就是“不消費”它。
往後查詢
?<=
用來做向後查詢,用法和向前查詢大同小異。
往前往後查詢相結合
我們直接匹配標籤中間的文字:
- 文字 -
<b>hello</b>
- 正規表示式 -
(?<=<[bB]>).*(?=</[bB]>)
嵌入條件
正規表示式裡的條件要用 ?
來定義。事實上,你們一家見過幾種非常特定的條件了。
-
?
匹配前一個字元或者表示式 -
/=
和?<=
匹配前面或者後面的文字,如果它存在的話。
嵌入條件語法也使用了 ?
,因為嵌入條件不外乎下面兩種情況
- 根據一個回溯引用來進行條件處理
- 根據一個前後查詢來進行條件處理
元字元 | 說明 |
---|---|
() | 定義一個子表示式 |
n | 匹配第n個子表示式 |
?= | 向後查詢 |
?<= | 向前查詢 |
?! | 負向前查詢 |
?<! | 負向後查詢 |
?() | 條件(if then) |
?()| : 條件(if then else)