Perl 常用的 regexp 規則列表


我們小時候學中文, 從來就不是從文法學起, 而是聽說讀寫很多例句, 腦中自然歸納出一些 (可能自己都說不太上來的) 規則。 聽說這樣的學習方式比較自然, 效果比較好; 當然, 花費的時間也相對較長。

筆者那個年代, 英文是從國中才開始學起, 已經不像小朋友有那麼充裕的時間。 因此老師/課本/參考書免不了要幫我們整理文法以加速學習: 什麼八大詞類, 直述句/疑問句, 假設語氣, 從屬子句, ... 等等。

如果您還記得 「長線投資的電腦學習策略」 裡面提到: 今日的資訊教育實在應該以英文數學教育為師, 就能理解為什麼要談這些了。 筆者認為像是 regexp 及命令列這類東西具有長遠投資價值, 但因為要記的文字內容較多而稍嫌困難, 應該可以用學中文的方式來教/學。 所以前兩篇的重點不在有系統/有組織, 而在令讀者熟悉/培養感覺。 不過 regexp 較之中英文, 簡單很多, 如果想把重要的符號及語法用有系統/有組織的方式列出來, 其實也並不會太嚇人。 這篇的目的就是要給那些與我學英文有相同 (不太好的?) 習慣, 迫不及待想看清楚規律的讀者看的。 但請記住: 沒有看過例子的規則, 其實都不算真的學過。 所以看這篇時, 請把它當做像英文文法書一樣, 用來複習/預習/查詢, 並且在腦中多回想/設想例子; 請不要認真地死背所有規則。 有些沒有教過的東西, 後面的章節會詳述。

一、 常用符號

常用的 regexp 符號可以大致分為三類:

  1. 比對 「一個字元」 的符號:
    • [...] ... 當中任何一個字元
    • [^...] 除了 ... 之外的任何一個字元
    • . 任何一個字元
  2. 具有 「定位」 功能, 但本身不吃掉任何字元的 anchor:
    • ^... 以 ... 開頭的字串
    • ...$ 以 ... 結尾的字串
    • \b 文數字/非文數字 的邊界。
  3. 計數用, 表達 「前面的樣版重複出現多少次」 的 quantifier:
    • {5} 重複 5 次
    • {3,7} 重複 3 到 7 次
    • {3,} 重複至少 3 次
    • ? 可有可無。 相當於 {0,1}
    • * 重複出現任意次, 包含 0 次。 相當於 {0,}
    • + 重複出現任意次, 至少 1 次。 相當於 {1,}
    所謂 「前面的樣版」 可能是一個字元, 也可能是由小括弧括起來的一長串

Quantifiers 有 "不貪婪版本": 例如 +? 是 + 的 "不貪婪版"; *? 是 * 的 "不貪婪版"。

小括弧 (...) 有三個常用的效果: 第一, 它讓 quantifier 可以作用在一串符號上, 而不只是作用在一個字元上。 第二, 小括弧裡面可以用 | 切成幾段, 表達 「這個字串或那個字串」。 (...|...|...) 可以說是 [...] 的升級版。 第三, 小括弧裡面的東西, 可以用 $1、 $2 撈出來用。 (下詳)

二、 為何選 perl?

我們之所以選擇 perl 來教 regexp, 有好幾個原因。 第一, perl 有一個很容易記的規則: 凡是標點符號, 加上倒斜線, 一定沒有特殊意義。 像 grep 或 sed 的 regexp 就有點複雜。 (所以我用 grep 時, 喜歡用 grep -P ... 採用 perl 相容模式, 腦袋比較不會打結。)

第二, perl 替最常用的 [...] 定義了簡寫:

  1. \d 其實就是 [0-9], "任何一個數字"
  2. \D 其實就是 [^0-9], "任何一個非數字"
  3. \w 其實就是 [a-zA-Z0-9_], "任何一個文數字"
  4. \W 其實就是 [^a-zA-Z0-9_], "任何一個非文數字"
  5. \s 其實就是 [ \t\n], "任何一個空白類字元"
  6. \S 其實就是 [^ \t\n], "任何一個非空白類字元"

這裡有關 \w 與 \W 的說明並不嚴謹. 若要處理英文以外的西方語言, 請參考 perlre(1) 與 perllocale(1)。

第三, 後來很多其他軟體都宣告支援 PCRE, 也就是 Perl-Compatible Regular Expressions, 可以說 perl 的 regexp 已成為軟體界共同的標準。

第四, 請見下一節。

三、 三種常用句型

perl 的彈性很大, 小小的變化就可以造出三種不同的句型, 應付常用的搜尋/代換工作 (實際上 一句 perl 能夠寫出的簡單變化還多得是; 不過為了怕嚇到讀者, 筆者必須克制一下, 就此打住): [圖解 'regexp 的三種常用句型']

  1. 搜尋, 並印出整列: perl -ne 'print if /.../' (簡略寫法)
    或是正式寫法: perl -ne 'print if m/.../' (m 表示 "match" 比對)
    效果約略等同於 grep -P '...' 這裡的 -P 就是 perl-compatible 的意思。
  2. 搜尋, 精確列印 (不要前後文): perl -ne 'print "$1\n" if /..(..)../'
    效果約略等同於 grep -Po '..' (但 grep 無法 print 指定僅印比對字串的一小部分)
  3. 代換: perl -pe 's/.../.../g'
    效果約略等同於 sed 's/.../.../g'。 但 sed 不支援 PCRE, 所以我只有很簡單的情況才用 sed。

這裡的 $1 表示 "第一對小括弧裡面的東西", $2, $3, ... 類推。 在 "搜尋, 精確列印" 句型, 及 "代換" 句型當中, 可以用來指稱比對到的字串的一部分, 再把它的變形印出來。 例如 ip.txt 這個文字檔記錄了一些套件的大小, 類別, 名稱三個欄位的資訊。 想把第二與第三個欄位對調過來, 可以用 "搜尋, 精確列印" 句型: perl -ne 'print "$1 $3 $2\n" if /(\W+)\s+(\W+)\s+(\W+)/' ip.txt 也可以用 "代換" 句型: perl -pe 's/(\W+)\s+(\W+)\s+(\W+)/$1 $3 $2/' ip.txt 當然如果資料有點不整齊, 像 這樣, 那就只能用 "代換" 句型了。

四、 其他常用技巧

在比對語法 m/.../ 或代換語法 s/.../.../ 後面, 都可以加上一些選項, 微調比對或代換的效果:

  1. i 忽略大小寫 (ignore)
  2. g 整列全面代換 (global)

如果你的 regexp 裡面正好用到 / 那麼也可以改用其他符號作為分隔符號, 但這時代表 「比對字串」 的 m 就不能省略。 例如 m#...#s|...|...| 也都可以。

五、 進階: 段落模式

通常 regexp 以列為單位在處理資料。 如果每一筆資料橫跨好幾列, 那就要用 -000 選項叫 perl 進入 「段落模式」 (paragraph mode), 把資料視為 「以空白列分隔的許多段落」。 準備資料時, 要在段落之間插入空白列; 同一段落的資料裡面不能有空白列。

在這個模式下, . 不會比對到換列字元。 如果希望它比對到換列字元, 需要加上 s 選項。

在這個模式下, ^ 跟 $ 分別比對到 「整段的頭」 跟 「整段的尾」。 如果希望它們比對 「一列的頭」 跟 「一列的尾」, 需要加上 m 選項。