正規表示式是個強大的工具,定義了明確的定串比對規則,來幫助使用者把梳字串,在很多程式都有支援,如 Linux shell 的grep, awk, sed,vim的搜尋取代功能等, C語言的scanf其實也有支援,可惜正規表示法規則甚多,一時之間難以記全。
這篇主要記錄正規表示法的規則,整理自"Practical Programming in Tcl and Tk"第十一章,給自己做個參考:
一般字元與跳脫字元:
如果要比對字串,最簡單的規則就是:把字元寫出來,寫 a 就會比對出a,夠簡單吧。
但這樣就沒啥變化了,如果我要比對a, aa 或aaaaa…aa呢?
如果全部的東西都是直接比對,就少了這種彈性,所以正規表示式定義下例幾個字元為跳脫字元,平時做為特殊處理之用,要比對他們需要特殊規則,包括:
.*+?()|[]^$\
在比對中寫下這些符號的功能之後會一一介紹到,如果是要比對這些符號,就用backslash \ 跳脫。
例如要比對\,就需要寫\\
而非只有\
字組(character sets)
如果我們要寫一個讓使用者輸入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,但加上 \{,\}
後則是跳脫為特殊意思。
數量(quantifiers)比對與比對順序:
我們只要使用者輸入「o某字z」,他要orz, otz,omz 我才不管呢?
首先使用 .
任意一個字元,跟shell的不太一樣,這要小心。
上面的比對,我們可以寫 o.z
那針對不同的數量,我們就需要數量字符:
char | quatity |
---|---|
* | 0~∞次 |
+ | 1~∞次 |
? | 0~1次 |
{m,n} | m~n次,其中n可以不填,表示∞ |
所以說,上面的比對可以用 a+ 完成。
重複的可以用()包起來,[]裡面的重複也OK,將數量字符直接加在下括號的後面即可。 比較重要的是 * 這個符號用起來要小心,這個比對表示任意字符重複0~n次,有時會造成不預期的結果。這就牽扯到正規表示法的比對規則了,如果一個match在字串中有多個相符的比對時,其規則如下:
- 各相符比對,最接近開頭者優先
- 如果是一樣的開頭,預設最長者優先,這個可以改換為nongreedy,即較短者優先
所以如果用 [a-z]* 去比對 123abc,一般可能會預期取出abc,但不對,在123前的開頭的空字串才是優先match的對象,一般都建議用+強制一定要有match來避免這樣的狀況。
字集(character classes)
字集為一些設定好的字元組合,寫法為:[: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 | 空白類字元:空白, \n, \r, tab, vertial tab, form feed 有人知道後兩個是蝦毀嗎… | \s |
cntrl | 控制字符:ASCII 0~31 | |
< > | 分別比對單定開頭與結尾 |
定位字符(anchoring):
這部分兩個定位就是^和$,一行的開頭和結尾
反斜線跳脫:
利用反斜線的跳脫字符,可以match一些特殊的字符,主要分為四類:
- 特殊符號類
- 與字集相同的設定,可以match一個字集
- 定位字符類
- 數字指定類
整理如下:
特殊符號類:
表示法 | 比對 | 等同符號表示 |
---|---|---|
\a | Alert character | |
\b | Backspace character | \u0008 |
\e | Escape character | \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 |
字集類:
表示法 | 比對 | 等同表示法 |
---|---|---|
\d | 數字 | [[:digit:]] |
\D | 非數字 | [^[:digit:]] |
\s | 空白類字符 | [[:space:]] |
\S | 非空白類字符 | [^[:space:]] |
\w | 字母+數字+底線 | [[:alnum:]] |
\W | 非以上組合 | [^[:alnum:]] |
字位字符類:
表示法 | 比對 |
---|---|
\A | 字串開頭 |
\Z | 字串結尾 |
\m | 單字開頭 [[:<:]] |
\M | 單字結尾 [[:>:]] |
\y | 單字開頭或單字結尾 |
\Y | 非\y |
數字指定類:
表示法 | 比對 |
---|---|
\unnnn | 16-bits Unicode character code |
\Unnnnnnnn | 32-bits Unicode character code |
\xhh | Consumes all hex digits after \x, 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:
這個規則比較重在要把內容存到暫存裡的狀況,一般的狀況下似乎不太用到。
在正規表示式中除了直接match的結果外,還可以用小括號()來記錄子比對的結果:例如(.*?),除了match整段文字外,可以取出兩個tag中間的文字內容。
要取出這個這個比對結果,可以用Back reference,使用的方式是 \1, \2, \3,依數字對應到各比對結果,比對結果的排序為"外到內,左到右”,如下所示:
(1(2))(3),注意 \1 是包括自己跟 \2 的內容。
look ahead:
另一方面,如果我們只想要比對,卻懶得管一些比對的內容呢?
這就需要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的差異點在哪我就不是很確定了。
其實主要的功能就上述這些,我提的那本書還有兩個功能,一個是collating element [.identifier.],只是書裡沒說得很清楚,我也還沒試出幹什麼用的…; 另一個是Equivalent class [=char=],只有比對Unicode例如 [=o=]可以比對 o, ō, ŏ,但平常好像也用不到。
如果是有關C語言中,有關scanf的部分,可以參考scanf的整理,一些基本的正規表示法語法在scanf中其實是有支援的,請見:
scanf整理