但因為要記的文字內容較多而稍嫌困難,
應該可以用學中文的方式來教/學。 所以前兩篇的重點不在有系統/有組織,
而在令讀者熟悉/培養感覺。 不過 regexp 較之中英文, 簡單很多,
如果想把重要的符號及語法用有系統/有組織的方式列出來,
其實也並不會太嚇人。 這篇的目的就是要給那些與我學英文有相同
(不太好的?) 習慣, 迫不及待想看清楚規律的讀者看的。 但請記住:
沒有看過例子的規則, 其實都不算真的學過。 所以看這篇時,
請把它當做像英文文法書一樣, 用來複習/預習/查詢,
並且在腦中多回想/設想例子; 請不要認真地死背所有規則。
有些沒有教過的東西, 後面的章節會詳述。
一、 常用符號
常用的 regexp 符號可以大致分為三類:
比對 「一個字元」 的符號:
[
...
]
... 當中任何一個字元
[^
...
]
除了 ...
之外的任何一個字元
.
任何一個字元
具有 「定位」 功能, 但本身不吃掉任何字元的
anchor
:
^
... 以 ... 開頭的字串
...
$
以 ... 結尾的字串
\b
文數字/非文數字 的邊界。
計數用, 表達 「前面的樣版重複出現多少次」 的
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 替最常用的
[
...
]
定義了簡寫:
\d
其實就是
[0-9]
,
"任何一個數字"
\D
其實就是
[^0-9]
,
"任何一個非數字"
\w
其實就是
[a-zA-Z0-9_]
,
"任何一個文數字"
\W
其實就是
[^a-zA-Z0-9_]
,
"任何一個非文數字"
\s
其實就是
[ \t\n]
,
"任何一個空白類字元"
\S
其實就是
[^ \t\n]
,
"任何一個非空白類字元"
這裡有關 \w 與 \W 的說明並不嚴謹. 若要處理英文以外的西方語言,
請參考 perlre(1) 與 perllocale(1)。
第三, 後來很多其他軟體都宣告支援 PCRE,
也就是 Perl-Compatible Regular Expressions,
可以說 perl 的 regexp 已成為軟體界共同的標準。
第四, 請見下一節。
三、 三種常用句型
perl 的彈性很大, 小小的變化就可以造出三種不同的句型,
應付常用的搜尋/代換工作 (實際上
一句 perl
能夠寫出的簡單變化還多得是;
不過為了怕嚇到讀者, 筆者必須克制一下, 就此打住):
搜尋, 並印出整列:
perl -ne 'print if /.../'
(簡略寫法)
或是正式寫法:
perl -ne 'print if
m
/.../'
(
m
表示 "match" 比對)
效果約略等同於
grep -P '...'
這裡的 -P 就是 perl-compatible 的意思。
搜尋, 精確列印 (不要前後文):
perl -ne 'print "$1\n" if
/..(..)../'
效果約略等同於
grep -Po '..'
(但 grep 無法 print 指定僅印比對字串的一小部分)
代換:
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/.../.../
後面, 都可以加上一些選項, 微調比對或代換的效果:
i 忽略大小寫 (ignore)
g 整列全面代換 (global)
如果你的 regexp 裡面正好用到 / 那麼也可以改用其他符號作為分隔符號,
但這時代表 「比對字串」 的 m 就不能省略。 例如
m#...#
或
s|...|...|
也都可以。
五、 進階: 段落模式
通常 regexp 以列為單位在處理資料。
如果每一筆資料橫跨好幾列, 那就要用 -000 選項叫
perl 進入 「段落模式」 (paragraph mode),
把資料視為 「以空白列分隔的許多段落」。
準備資料時, 要在段落之間插入空白列;
同一段落的資料裡面不能有空白列。
在這個模式下, . 不會比對到換列字元。
如果希望它比對到換列字元, 需要加上 s 選項。
在這個模式下, ^ 跟 $ 分別比對到 「整段的頭」 跟 「整段的尾」。
如果希望它們比對 「一列的頭」 跟 「一列的尾」, 需要加上 m 選項。