正規表示式(Regular
expression, RE)--規則篇:
正規表示式是個強大的工具,定義了明確的定串比對規則,來幫助使用者把梳字串,在很多程式都有支援,如Linux
shell 的grep,
awk, sed,vi(m)的搜尋取代功能等,C語言的scanf其實也有支援,可惜正規表示法規則甚多,一時之間難以記全。
這篇主要記錄正規表示法的規則,整理自“Practical
Programming in Tcl and Tk”第十一章,給自己做個參考:
一般字元與跳脫字元:
如果要比對字串,最簡單的規則就是:把字元寫出來,當寫:
a,就會比對出a,夠簡單吧。
但這樣就沒啥變化了,如果我要比對a,
aa
或aaaaa...aa呢?如果全部的東西都是直接比對,就少了這種彈性,所以正規表示式定義下例幾個字元為跳脫字元,平時做為特殊處理之用,要比對他們需要特殊規則,包括:
.*+?()|[]^$\
在比對中寫下這些符號的功能之後會一一介紹到,如果是要比對這些符號,就用backslash
\ 跳脫。
例如要比對\,就需要寫\\而非只有\
字組
(character
sets)及多重選項
(alternation):
如果我們要寫一個讓使用者輸入y/n的程式,使用者可能會填y/Y/n/N裡面一個,因此就有了字組的概念,字組利用跳脫字元[],讓某一個位置有多重選項:
[yY],則y或Y都可以match
在[]裡面可以使用“[x-y]”的規則,來比對某區間的字,例如:
[0-9]比對所有數字,
[a-z]比對小寫的英文。
這裡要注意的是,隨著語系的選擇,[x-y]會有不同的範圍,不過一般都是以LANG=C的情況下去設計,也就是0-9A-Za-z的排序。
子規則1:反向用[^],例如我要比對“不是<”,就用[^<],如果是[^a-z],就會排除所有小寫英文。
子規則2:比對跳脫字元除了]需要在[]要排第一位、\仍要跳脫外,其他在[]就不用跳脫,直接寫就可以
例如下行會比對出所有的跳脫字符:
[][.*+?()|\\]
另外則是{}這個直接寫就是直接match,但加上\{,\}後則是跳脫為特殊意思。
-
如果像上述,我們要比對a,aa或aaaaaaa...aa,或者我們只要使用者輸入「o某字z」,他要orz,
otz,omz 我才不管呢?
首先使用的是「.」,這是RE中的「任意一個字元」,跟shell的不太一樣,這要小心。
所以上面的比對,我們可以寫 o.z
那針對不同的數量,我們就需要數量字符:*+?
以及{m,n}
字符
|
代表數量
|
*
|
0~∞次
|
+
|
1~∞次
|
?
|
0~1次
|
{m,n}
|
m~n次,其中n可以不填,表示∞
|
所以說,上面的比對可以用 a+
完成。
重複的可以用()包起來,[]裡面的重複也OK,將數量字符直接加在下括號的後面即可。
比較重要的是 * 這個符號用起來要小心,這個比對表示任意字符重複0~n次,有時會造成不預期的結果。這就牽扯到正規表示法的比對規則了,如果一個match在字串中有多個相符的比對時,其規則如下:
各相符比對,最接近開頭者優先:
如果是一樣的開頭,預設最長者優先,這個可以改換為nongreedy,即較短者優先。
所以如果用 [a-z]*
去比對 123abc,一般可能會預期取出abc,但不對,在123前的開頭的空字串才是優先match的對象,一般都建議用+強制一定要有match來避免這樣的狀況。
-
字集為一些設定好的字元組合,寫法為:[:identifier:]
整理可選用的identifier如下:
identifier
|
比對:
|
等同表示法:
|
lower
|
英文小寫
|
[a-z]
|
upper
|
英文大寫
|
[A-Z]
|
alpha
|
英文字母
|
[A-Za-z]
|
digit
|
數字
|
[0-9]
|
xdigit
|
十六進位數字
|
[0-9a-fA-F]
|
alnum/print
|
以上總集合
|
[0-9a-zA-Z]
|
punct
|
標點符號例如 `'”/<>之類
|
|
graph
|
非控制字符或空白類字符,其實就是以上總集合
|
我猜可以寫成[[:alnum:][:punct:]]
|
blank
|
空白和tab
|
[
\t]
|
space
|
空白類字符:空白,換行,回車,tab,vertical
tab,form
feed
有人知道後兩個是蝦毀嗎…
|
\s
|
cntrl
|
控制字符:ASCII
0~31
|
|
<
>
|
分別比對單定開頭與結尾
|
|
-
這部分兩個定位就是^和$
=w=
-
利用反斜線的跳脫字符,可以
match一些特殊的字符,主要分為四類:特殊符號類;與字集相同的設定,可以
match一個字集;定位字符類;數字指定類,整理如下:
1 特殊符號類:
表示法:
|
比對:
|
等同符號表示:
|
\a
|
Alert
character.
|
|
\b
|
Backspace
character.
|
\u0008
|
\e
|
Escape
character(ESC)
|
\u001B
|
\n
|
Newline
|
\u000A
|
\B
|
Backslash
|
\\
|
\0
|
NULL
|
\u0000
|
\r
|
Carriage
return
|
\u000D
|
\t
|
Horizontal
tab
|
\u0009
|
\f
|
Form
feed
|
\u000C
|
\v
|
Vertical
tab
|
\u000B
|
\cx
|
Control-x
|
|
2 字集類:
表示法
|
比對
|
等同表示法
|
\d,
\D
|
數字,
非數字
|
[[:digit:]]
,[^[:digit:]]
|
\s,
\S
|
空白類字符,非空白類字符
|
[[:space:]]
, [^[:space:]]
|
\w,
\W
|
字母+數字+底線,
非以上組合
|
[[:alnum:]_]
, [^[:alnum:]_]
|
3
定位字符類:
表示法
|
比對:
|
\A,
\Z
|
字串開頭,
字串結尾
*還沒找到相對應的範例
|
\m,
\M
|
單字開頭,
單字結尾
[[:<:]]
[[:>:]]
|
\y,
\Y
|
單字開頭或單字結尾,
非\y
*還沒找到相對應的範例
|
4
數字指定類:
表示法:
|
比對:
|
\unnnn,
\Unnnnnnnn
|
16-bits,
32-bits Unicode character code.
|
\xhh
Consumes all hex digits after \x.
|
An
8-bit hexadecimal character code.
|
\x,
\xy, \xyz
|
xyz為數字,這個可以是back
reference,或者是8-bit
octal character code
|
Nongreedy(非貪婪?這怎麼翻
(yay)):
之前提過RE的規則是愈長愈好,設定Nongreedy即是變成愈短愈好,使用的是? 加在第三項的數量(quantifiers)比對後面,比如說我們要match整個輸入中的第一行,如果寫:
.+\n 這樣會match整個輸入,直到最後一個\n符號,其中包括很多很多換行符號。
.+?\n 設定+為nongreedy,則遇到第一個\n就會結束比對
或者我們要抓<title></title>中間的文字:
所有的數量比對加?在後面都會變成non-greedy,??代表0次優先,{1,3}?則會以1,2,3的順序去match。
Back
reference與
look
ahead:
這個規則比較重在要把內容存到暫存裡的狀況,一般的狀況下似乎不太用到。
在正規表示式中除了直接match的結果外,還可以用小括號()來記錄子比對的結果:
例如<title>(.*?)</title>,除了match整段文字外,可以取出兩個tag中間的文字內容。
要取出這個這個比對結果,可以用Back
reference,使用的方式是\x,x為數字,依數字對應到各子比對結果,子比對結果的排序為“外到內,左到右”,如下所示:
(一(二))(三),注意(一)是包括自己跟(二)的內容。
另一方面,如果我們只想要比對,卻懶得管一些比對的內容呢?這就需要look
ahead,我想到最需要誕個功能的地方是rename,我要改掉所有txt檔,卻只要取出檔名的部分,這時候就需要look
ahead'
例如我要取出副檔名為.txt的文件名稱:
^.*\.txt$,可以,但記錄下來的是:lalala.txt,.txt是不需要記錄的
positive
look ahead:^.*(?=\.txt)$,記錄的就只有lalala
negative
look ahead:^.*(?!\.txt)$,這反過來,比對出非.txt結尾的檔案名稱。
還有一個是(?:pattern),同樣也是不記錄pattern中的內容,但跟look-ahead的差異點在哪我就不是很確定了。
其實主要的功能就上述這些,我覺得主要會用到的大概只到第6點,我在vim會用到第8點,我提的那本書還有兩個功能,一個是collating
element [.identifier.],只是書裡沒說得很清楚,我也還沒試出幹什麼用的…;另一個是Equivalent
class [=char=],只有比對Unicode例如[=o=]可以比對 o,
ō,
ŏ,但平常好像也用不到。
這篇主要整理規則,之後會寫一篇有關應用方面的實例與方法。
如果是有關C語言中,有關scanf的部分,可以參考scanf的整理,一些基本的正規表示法語法在scanf中其實是有支援的,請見: