十分鐘正則表達式入門教程

本教程主要針對不熟悉或者對概念上略有模糊的人。我會大致解釋一下正則表達式的各個概念和作用,至於實際使用還需要各位自己反覆練習。另外需要更細緻或者深入的教程可以依次參考以下內容:

  • 本專欄作者之一 @shell von 的30分鐘學正則,口水話雖然多了一點,但也大致 cover 的相關的概念
  • 已經更新維護了十多年的 正則表達式30分鐘入門教程,最早就是通過這篇教程入門
  • Orielly 的《精通正則表達式(Mastering Regular Expression)》第三版

釋題

正則表達式使用單個字元串來描述、匹配一系列符合某個句法規則的字元串。在很多文本編輯器里,正則表達式通常被用來檢索、替換那些符合某個模式的文本。

正則表達式流派很多,不同語言之間使用方式和結果也有不同程度上的差異。所以十分鐘能夠覆蓋的只可能是概念上的內容。只是由於後面 Perl 6 系列的文章多少回引用到相應的概念,所以在這裡有這麼一個導入是非常有必要的,同時也作為一個教程給初學者用。

為了方便使用,所有的代碼採用的是 JavaScript,你可以隨時開啟瀏覽器的開發者工具來測試和學習。

一個簡單的示例

text = "the quick brown fox jumps over the lazy dog";text.match(/.[aeiou]./g);// => [ "he ", "qui", "row", "fox", "jum", " ov", "he ", "laz", "dog" ]

處在 //g 之間的就是正則表達式本體。這段正則表達式的作用是匹配一個三個字元、其中第二個字元是原因字母( aeiou 之一)的子串。

當然其中有一個例外就是,overver 本來也應該被匹配到才對。但由於 ov 這個子串已經被匹配了,所以粗線了這麼一個意外的 case。

Pattern 模式

正則表達式表示的是一個特定的匹配規則,又叫匹配模式,就是你想要匹配的內容的直接描述。

比如

text.match(/quick/g) // => [ "quick" ]

Alternative 分支條件

有時候我們可能希望匹配多種情況,比如兩個不同的單詞,這樣可以使用 | 來分隔不同的模式:

text.match(/fox|dog/g) // => [ "fox", "dog" ]

| 符號可以連續使用,比如

text.match(/quick|brown|lazy/g)

Grouping 分組

() 可以用於分組,亦即,把一個正則表達式分解成幾個子表達式。

text.match(/(d|f)o/g) // => [ "fo", "do" ] from "fox" and "dog"

Character Class 字元類

比如我們希望匹配所有的字母,或者一些複雜一點的集合,當然分組配合分支條件的話就可以幫我們實現目標了:

text.match(/(b|c|d|f|g|h|j|k|l|m|n|p|q|r|s|t|v|w|x|y|z)(a|e|i|o|u)/g)// => [ "he", "qu", "ro", "fo", "ju", "ve", "he", "la", "do" ]

不過字元類給了我們更直接的方式:

text.match(/[a-z][aeiou]/g)// => [ "he", "qu", "ro", "fo", "ju", "ve", "he", "la", "do" ]

當然了僅僅是能得到一樣的結果,[a-z] 字元類相比於前面我們寫的那一堆還是不夠的細化的。

另外正則表達式還有一些預定義的字元類:

  • w 匹配所有字母、數字、連字元和下劃線
  • d 匹配所有數字
  • s 匹配所有空白字元
  • .(英文句號) 匹配任意字元

如果你想要表示匹配不存在某字元類的字元,可以使用[^],或者,把預定義字元類的符號改成大寫字母。

所以這個時候,最前面的那個例子你們應該就能夠看得懂了。

Repeating 重複

如果我們希望一個模式可以被重複匹配,可以用下面幾種方式來表示:

  • ? 重複零次或一次,亦即,該模式是可選的。
  • * 重複零次或多次。
  • + 重複一次或多次 。
  • {m} 重複 m 次。
  • {m,} 重複 m 或者更多次。
  • {m, n} 重複 m 到 n 次。

比如,尋找出現連續5個字母的子串:

text.match(/w{5}/g) // => [ "quick", "brown", "jumps" ]


直到目前,所有的正則表達式還都能跟正則語言掛鉤,但後面就開始魔性了。

Capturing & Back Reference 捕獲和後向引用

() 不僅僅有分組的作用,還會把匹配到的子串作為一個結果以方便後面引用。

先看例子:

text.match(/(w+).*1/g) // => [ "the quick brown fox jumps over the" ]

1 就是用來引用前面分組中捕獲到結果的一種方式。

我們依次拆分這個正則表達式:

  • (w+) 匹配一個單詞並捕獲
  • .* 匹配任意長的任意字元
  • 1 匹配前面捕獲的單詞

換句話說就是匹配兩個一樣的單詞和他們之間的內容。而示例中的文本,只有兩個 the 能滿足這個條件。

捕獲是按順序的,所以對於更多的捕獲可以依次用 12……來引用他們。但是當表達式異常複雜的時候很難確定順序的,這個時候我們可以使用命名捕獲

text.match(/(?<word>w+).*k<word>/g)// => [ "the quick brown fox jumps over the" ]

(?<>)用來給分組命名,k<>用來引用命名分組。

當然我們也可以選擇不對分組進行捕獲。使用(?:)就好。:)

Assertion 斷言

(其他翻譯可能把通用的斷言也稱作「環視」)

斷言用於指定所匹配的位置是否滿足要求。其中有一些通用斷言(也有人叫 Anchor 錨):

  •  用來匹配單詞的開始/結束,B 用來匹配相反的位置。
  • ^$ 用來匹配文本的開始與結束。

示例:

text.match(/w{3}/g) // => [ "the", "fox", "the", "dog" ]text.match(/w{3}B/g)// => [ "qui", "bro", "jum", "ove", "laz" ]

以及一些更靈活的斷言:

  • (?=) 斷言緊隨其的位置匹配某模式
  • (?<=) 斷言其的位置匹配某模式
  • (?!) 斷言緊隨其的位置不匹配某模式
  • (?<!) 斷言其的位置不匹配某模式

text.match(/w(?=o)/g) // => [ "r", "f", "d" ]text.match(/(?<=o)w/g) // => [ "w", "x", "v", "g" ]text.match(/w(?![aeiou])w+/g) // 第二個字母不是母音的單詞// => [ "the", "brown", "over", "the" ]


未提及的內容

如前所述,本文僅能概括部分內容,其中另有一些作為深入研究或者參考的內容我把他們列出來,大家可以自行搜索查詢,或者去前面我推薦的內容看:

  • 貪婪和懶惰匹配
  • 平衡和遞歸
  • 回溯
  • 語言/流派相關的正則內容
  • 實現原理、自動機

參考

  • Modified ECMAScript regular expression grammar C++ Reference
  • Regular expression Wikipedia
  • perlre - perldoc.perl.org Perl Doc

推薦閱讀:

EOS初學者入門指南(上)
思維導圖入門應該知道的一些小知識
「教練,我想打籃……額不,做皮具,怎麼入門?」
MATLAB-Dijkstra演算法
篆隸入門是專業學習書法的唯一捷徑!能給個理由嗎?⊙?⊙

TAG:正則表達式 | 入門指南 |