正規表示式簡明教程

老姚發表於2019-07-16

說明:本文是我在公司技術講座上某次分享的總結。

正則是字串匹配模式,在處理文字時很有用。最常見的操作就是用於查詢和替換。

說到處理文字,其實我們每天敲的程式碼就是文字,因此常用的程式碼編輯器的查詢替換工具基本都支援正則語法的。

vscode編輯器查詢替換工具

先說明一下,接下來的內容都以《We will rock you》的歌詞測試文字。

點選展開歌詞
Buddy, you're a boy make a big noise
Playing in the streets gonna be a big man someday
You got mud on your face
You big disgrace
Kicking your can all over the place
Singing
We will, we will rock you
We will, we will rock you
Buddy you're a young man, hard man
Shouting in the street gonna take on the world someday
You got blood on your face
You big disgrace
Waving your banner all over the place
We will, we will rock you

使用的正則測試工具是 Regex 101

這裡建議讀者看的過程中,同時開啟該網站,把歌詞貼進去,每個案例都驗證一遍。也建議稍微改動一下正則,看看匹配結果仍是否與自己的理解一致。跟著動手,學習效果要好一些。

1. 精確匹配

正則是用來描述字串的一種模式(pattern),或者說規律。最平凡的用法,就是精確查詢。比如我要找到歌詞中的所有“the”。正則寫成 the 即可。

正規表示式簡明教程

上圖只找到了一個 the,而不是所有的。這是因為正則本身是分兩部分的,一部分是模式,另一部分是修飾符(flags,或者叫標誌位)。一個常用的修飾符是 g,它單詞 global 的簡寫,表示全域性查詢。

正規表示式簡明教程

此時,我們找到了所有“the”。接著我們再找所有“we”。

正規表示式簡明教程

然而,同時我們也希望找到文字中“We”,w 字元是大寫的。此時可以用另外常見的識別符號 i,單詞 ignoreCase 或者 insensitive 的首字母,表示忽略大小寫。

正規表示式簡明教程

無論 the 或 we,這種模式匹配都是精確匹配,如果正則只是輸入什麼就查詢什麼,那麼其存在的意義就沒有那麼大。而它的強大之處在於能實現模糊匹配。

2. 橫向模糊匹配

比如我們想找到歌詞中所有連續出現的“e”。

正規表示式簡明教程

圖中正則形如 p{m,n},表示 p 至少連續出現 m 到 n 次(包括m、n)。p 可以是一個子模式,不一定只是一個字元。

正規表示式簡明教程

上圖中,為了測試我修改了部分歌詞。其中正則使用了括號,括號如你所料一樣,起到了高優先順序的作用。表示 noise 這個整體重複出現了 1 到 3 次。

不知道此時你是否有疑問,{1,3} 表示 1 到 3 次。為啥上面的匹配結果只有一個呢?而不是匹配到 3 個 noise。又或者 noisenoise 和 noise,這兩個結果呢?

這是因為量詞有貪婪和惰性之分。{1,3} 這個量詞是貪婪的,能滿足條件的話,它會盡可能多地匹配。可以在量詞的後面加個問號,讓其變為惰性的。

正規表示式簡明教程

確實夠懶得的,找到一個就滿足了。量詞後面的這個問號,彷佛是在問量詞,“可以別再貪了嗎?”

量詞的含義清楚了,下來我們來看一些簡寫形式。

  • * 等價於{0,}。即任意多個。
  • + 等價於{1,}。即至少一個
  • ? 等價於{0,1}。即有一個或者沒有
  • {m} 等價於{m,m}

這裡要說明的是 ? 這時就可能兩個含義。即一個表示惰性模式,一個表示量詞。

其實二者很好區分,在量詞之後的 ? 才表示惰性匹配。比如正則 bo??y,第一個問號表示量詞 {0,1},第二個表示量詞是惰性的。

量詞的存在,能讓正則可以模糊匹配,即很少的模式程式碼就能匹配一長串。我稱之為橫向模糊匹配。還有一種縱向的模糊匹配。

3. 縱向模糊匹配

假設歌詞中有幾處不小心把“rock”寫成“ruck”。我們需要找到二者,可以使用字符集 r[ou]ck。效果如下:

正規表示式簡明教程

其中 [ou],這種方括號括起來的模式就是字符集。它是一個集合,匹配“o”或者“u”。又比如我們要找到所有 a 到 e 的字元,可以寫成 [abcde]。這種連續的字元也可以簡寫成 [a-e]。

正規表示式簡明教程

字符集是集合的意思,而集合有補集。正則裡在方括號內開頭加上脫字元,來表示取反[^a-e],匹配一個不是 a、b、c、d、e 的某字元。

正規表示式簡明教程

字元類的含義搞清楚了,下來我們來看一下常見的簡寫形式

  • \d 等價於 [0-9]。表示是一位數字。digit 的首字母。
  • \D 等價於 [^0-9]。
  • \w 等價於 [0-9a-zA-Z_]。表示數字、大小寫字母和下劃線。word的首字母,也稱單詞字元。
  • \W 等價於 [^0-9a-zA-Z_]。
  • \s 等價於 [ \t\v\n\r\f]。表示空白符,包括空格、水平製表符、垂直製表符、換行符、回車符、換頁符。記憶方式:s是space character的首字母。
  • \S 等價於 [^ \t\v\n\r\f]。
  • . 等價於[^\n\r\u2028\u2029]。點是萬用字元,表示幾乎任意字元。

字符集是正則實現模糊匹配的另外一種方式,具體到某一位上,要匹配的字元可以是不確定的,我稱之為縱向模糊匹配。

量詞和字元組掌握了話,基本上正則問題能解決一多半。這裡再舉一個例子。找到所有以“ing”結尾的單詞。

正規表示式簡明教程

上面使用的是貪婪量詞,如果使用惰性量詞的話,情形會有所不同。

正規表示式簡明教程

此時,“singing”這個單詞分成了“sing”和“ing”。要完整的匹配一個單詞。需要匹配位置。

4. 匹配一個具體位置

比如匹配“you”這個單詞,可能會匹配到“your”中的 you。

正規表示式簡明教程

此時我們可以使用\b。b 是單詞 boundary 的首字母。它表示匹配一個位置,這個位置某一邊是\w,另一邊是\W。也就是一邊是單詞字元,一邊是非單詞字元,因此它叫單詞邊界。

如果對“位置”這一概念,理解得還是不太透徹,我們可以具體看看 \b 到底長什麼樣。

正規表示式簡明教程

注意上圖中得粉色虛線。它們就是一個個位置。請看看每一個是不是兩邊一個是單詞字元,另一個是非單詞字元。

位置也是有反義的。比如 \B 表示非單詞邊界。我們也可以看看。

正規表示式簡明教程

有了單詞字元後,要準確的匹配單詞“you”,可以使用\byou\b。

正規表示式簡明教程

除了單詞邊界這種位置之外,估計大家應該知道 ^ 和 $。它匹配整個文字的開頭和結尾。

正規表示式簡明教程

還記得前面我們找“we”嗎,如果我們想找到所有行開頭的 we 單詞。我們可以使用多行模式:

正規表示式簡明教程

此時修飾符裡多了一個 m,是 multiline 的首字母,表示多行匹配。所謂多行匹配,就是說 ^ 和 $,可以匹配行開頭和行結尾,不再侷限於整個文字的開頭和結尾。

除了 \b、\B、^、$ 外,還有一種斷言位置。比如 (?=p),表示模式 p 前面的位置。

正規表示式簡明教程

(?!p)是其反義。還有反向的斷言,例如 (?<=p),表示模式 p 後面的位置。或者說該位置的後面是 p。它也有反義的形式 (?<!p)。請讀者自己嘗試看看都匹配了啥。

關於位置這一塊兒,多說幾句。假如我想找到這樣的位置,該位置不能是開頭,並且後面的字元是 s,此時該怎麼做呢?

正規表示式簡明教程

(?!^) 其實就是 ^ 的反義。連續寫多個位置是沒有關係的。比如寫 ^^^^。

正規表示式簡明教程

需要注意的位置不同於字元,是不佔地方的,如果說是字元也可以,它則是空字元,沒有實際寬度的。

正則要麼匹配字元,要麼匹配位置。主體內容介紹完了,接下來查缺補漏。

5. 引用

street 裡有兩個 e,而 all 裡有連個 l。此時我想找到所有這樣的雙棒字母,該怎麼做呢?直接使用 .{2} 是不行的。因為它就是 .. 的簡寫形式,表示兩個任意字元。並沒有要求這兩個字元相同。

正規表示式簡明教程

此時,就涉及到了反向引用。參考如下寫法:

正規表示式簡明教程

\1 是反向引用,表示第一個括號裡捕獲的資料。那麼 \2 呢,表示第二個括號捕獲的。

正規表示式簡明教程

需要注意一點是這裡的括號,是平常的括號,而不是像 (?=p) 那樣特殊語法的括號。

括號捕獲的資料,不僅可以在正則裡反向引用。也可以配合宿主 API 來使用,外部引用。比如實現濾重:

正規表示式簡明教程

上面使用了替換,工具內部必然要用到宿主語言相關 API。$1 表示外部引用第一個分組捕獲的內容。

括號可以用來提供分組功能,又能捕獲資料。能否只讓括號充當分組功能呢?此時要使用非捕獲分組(?:p),而不是(p)。

6. 分支結構

比如我想找到所有的 face 和 place。此時該怎麼辦?

正規表示式簡明教程

管道符 |,表示或的關係,多選一。它從左到右面一個個嘗試,如果成功,就不再繼續嘗試了。可以說它是短路的、惰性的。比如用 you|your 去匹配 your 時,它只會匹配到 your 的前 3 個字母。所以分支順序不同結果可能也會不同。

正規表示式簡明教程

總結

本文,通過案例的形式覆蓋了正則常用語法。包括:

  • 量詞
  • 字符集
  • 分支結構
  • 修飾符
  • 位置
  • 引用
  • 常見簡寫

如果文中每個例子,你都自己手動輸入除錯並理解的話,你可以放心地說自己正則已經入門了。

如果想更全面深入理解JS正則的話,歡迎閱讀本人的 《JS正則迷你書》

當然,只掌握語法是肯定不夠的,還需要大量的練習。

有一個燒水理論,如果從來沒把水燒開過,不管燒幾次,水都是不能喝的。可一旦燒開過,哪怕放一陣子也是可以喝的。

初學又不用,很容易忘,就是這個道理。

一個很好練習的地方是 codewars,沒事多刷刷正則相關題目,用不上幾天就熟悉了。

希望有所幫助。

本文完。

相關文章