Regular Expression

正規表達式 (RE, Regex, Regular Expression) 是一種擷取規則文字的機制。其原理為:

  1. 建立文法 (Grammar) 表達式。
  2. 演算法根據其規則進行比對 (Match)。
  3. 若相符,將會把文法中的群組 (Group) 內容取出,稱為 Token。
  4. 若不相符,會跳過。
  5. 繼續尋找直到搜尋完畢。

在閱讀 Syntax 之前,可以先透過本節學習如何從有規律的文字中擷取需要的內容。

本節使用 JavaScript 表示 RE 文法,故會有夾注號 / 如:/re+/。使用其他程式語言時可忽略,按其規則為準。

推薦的測試網站:https://regex101.com

Rules

Base Grammar

除了特殊字元,RE 的文法等價於要解析的文字:

/abc/  // abc

特殊字元,作為起始即有特殊作用:., ?, +, *, |, [, (, ), $, ^, \

非特殊字元,可單獨作為表示,但是可配合特殊字元:,(comma), {, }, ]

Any Single Character

使用 . 表示任意單一字元,包含不可視字元。

Escape Character

跳脫字元 (Escape Character) 為反斜線 \

主要可以表示特殊規則,幫助縮減文法長度。

另外可以強迫特殊字元(包含自身)變成普通文字。

Invisible Characters

不可視字元 (Invisible Characters) 為顯示或列印時沒有顯示符號的字元,如下:

符號說明
空白 (Space)
\t縮進 (Tab)
Linux & MacOS: \n, Windows: \r\n換行 (Newline)

Repetition

單一文字重複規則如下:

/a{n}/  // 表示 a 重複 n 次
/a{n,}/  // 表示 a 最少重複 n 次,最多無限次
/a{n,m}/  // 表示 a 最少重複 n 次,最多 m 次

而簡化語法如下:

/a?/  // 表示 a 最少重複 0 次,最多 1 次(可寫可不寫)
/a+/  // 表示 a 最少重複 1 次,最多無限次
/a*/  // 表示 a 最少重複 0 次,最多無限次

若前一個是群組則會套用到群組的規則。

/(abc)+/  // abc, abcabc, abcabcabc, ...

Group

使用群組 (Group) 功能擷取文字,符號為 ()

定義 Group 0 為整段符合的文字,剩下的從 1 開始編號,重複的以此類推。

/(aaa)(bbb)/  // g0: aaabbb, g1: aaa, g2: bbb
/(aaa)(bbb)+/  // g0: aaabbbbbb, g1: aaa, g2: bbb, g3: bbb

若沒有要擷取文字,為了方便定義規則,可以宣告為匿名群組。

/(?:abc)+/

Logical Or

邏輯或 | 運算子能夠將多個類似的規則在同個文法中表現。

然而正規表達式的邏輯或運算子是最低優先權的,必須使用匿名群組等提升優先權。

/aa(?:bb|cc|dd)e/  // aabbe, aacce, aadde

List

清單 (List) 表示單一字元的多種選項,是邏輯或的一種縮寫,符號為 []

/a[bcd]e/  // abe, ace, ade
/a(?:a|b|c)e/  // 等價

為了方便,清單支援 ASCII Code 的範圍 (Range) 標示,且可以連續標示。 不過如果是其他字元,會以 ASCII 的編號表示。

/[0-9]/  // 0 ~ 9
/[a-zA-Z]/  // a ~ z, A ~ Z

另外清單前端加上 ^ 符號為反向集合,會對應到沒有顯示的字元,可以只標示例外即可。

/[^a-z]/

而清單也支援重複的定義。

/[qrs]{3}/  // qqq, rrr, sss, ...

Anchor

定位點 (Anchor) 可以幫助在文字中定位,不代表任何字元,常用的如下:

  • ^整個字串的開頭。
  • $整個字串的結尾。

Common Tokens

以下列舉常用的規則:

符號說明等價反向
\s不可視字元[\r\n\t ]\S
\d數字[0-9]\D
\w文字[0-9a-zA-Z]\W
\b文字邊界(定位點)(^\w\|\w$\|\W\w\|\w\W)\B

Flags

正規表達式的選項稱為 Flag,可能因為不同程式語言的規則而異。常用的如下:

Flag說明備註
global不只作用一次Python 不支援
multiple line啟用後,^$ 對應行的開頭和結尾,而非整個字串
insensitive忽略大小寫
extend啟用單行註解功能 # 並忽略空白標示JavaScript 不支援
stiky每次符合必須相連僅 JavaScript 支援
unicode支援萬國碼,\w 將對應所有其他字元
ascii僅支援 ASCII Code僅 Python 支援

在 JavaScript 中,Flag 可以寫在括弧後啟用。

/abc/gm

Token Example

試著思考題目,並參考正規表達式的撰寫思路。

CSV

逗號分隔值 (CSV, Comma-Separated Values) 通常為逗號、縮進、空白等符號分開數值。

0.01 0.06 -9.997
1.69 +40.44 4.6321

思路:

  • 數字在文法中可以表示成:

    /d+/
    
  • 浮點數可以表示成:

    /\d*\.?d+/
    
  • 帶有符號的浮點數可以表示成:

    /[+-]?\d*\.?d+/
    
  • 每行有三個數值,用空白分隔,可以表示成:

    /^([+-]?\d*\.?d+) ([+-]?\d*\.?d+) ([+-]?\d*\.?d+)$/m
    

可得:

Matchg1g2g3
10.010.06-9.997
21.69+40.444.6321

G Code

假設這是某台工具機的指令碼,你必須設計它的速度規劃。 在忽略換行記號的情況下,試著抓取以下每行 G code 中的 G 碼座標進給速度。 不相關的內容可以忽略。

%
O100
G00 G40 G49 G20 G90
N05 M10
N10 M05
N15 G91 G28 Z0
N20 G90
N25 T1 M06
N30 G00 Z.1
N35 G00 X2.5 Y2.5
N40 G01 Z-.25 F200
N45 G01 X5 Y5
N50 G00 Z.1
N55 G28 X0 Y0
N60 M05
N65 M45
%
  • 根據 N、G、X、Y、Z、F 排列順序,每個符號會跟隨一個整數帶符號的浮點數

    /N\d+/
    /X[+-]?\d*\.?\d+/
    
  • 而 N、X、Y、Z、F 為可選的項目。

    /(?:X([+-]?\d*\.?\d+))?/
    
  • 可以使用 \s* 代表間隔的空白,每行可排列成:

    /G(\d+)\s*(?:X([+-]?\d*\.?\d+))?\s*(?:Y([+-]?\d*\.?\d+))?\s*(?:Z([+-]?\d*\.?\d+))?\s*(?:F([+-]?\d*\.?\d+))?\s*/
    

可得:

Matchg1g2g3g4g5
100
240
349
420
590
691
7280
890
900.1
10002.52.5
1101-.25200
120155
1300.1
142800