大家來學VIM(一個歷久彌新的編輯器)[十]
?
在本系列文章一開始就說明了學 vi(m) 可以順便學規則表示式(regular expression,以下簡稱 regexp),那為什麼到現在才來講呢?因為 regexp 說簡單也算不很難,但您要深入去使用的話,有時會馬上看不出一個複雜的 regexp 在說些什麼的,就曾有人形容 regexp 為「有字天書」!而且在 vi(m) 整體都還沒一個概念就加入 regexp 的話,那後面的單元恐怕就沒人看了!而 regexp 各家有各家的 extensions,這也是大家視為畏途的原因之一,不過總是大同小異,只需注意一下就可以了。目前先不必管別家怎麼說,就讓 vim 暫時先成為我們的「標準」,以後碰到其它程式的 regexp 應該就可以觸類旁通。以下我們盡量由實例去瞭解。當然,小小的一篇文章是沒有辦法詳盡介紹,只能撿重點來說明了。如有疑問,可 :h pattern 或在 Un*x 系統中可 man 7 regex,甚至 man ed,man sed,man grep,man awk,man perlre 裡面也是會說些 regexp,但要注意和 vim 差異的地方!其中 perl 的 regexp 應該是最完整的了,如果您的系統沒有 perl 那應該是「稀有動物」了!:-) ㄟㄟㄟ!vim 只是一個編輯器,可不是獨立的程式語言!
?
?
基本的匹配
* 指前所綁住的字元或字元集合,出現 0 次或 0 次以上。\+ 和 * 作用相同,但不包括出現 0 次。\= 指前所綁住的字元恰好出現 0 或 1 次。\| 這是多選,就是 or 的意思,被 \| 隔開的 pattern,任一個符 合的話就算符合。\+, \=, \| 會加上一個 \,是因原字元在 vi(m) 就具有特殊意義,在一般的 regexp 中是 +,?,| 就可以了,只是提醒您一下,以免搞混了! 記住 * \+ 是不可數的!用辭不是是精確,只是幫助您記憶啦! 在 elvis 及 ed 中是使用 \? 來匹配出現 0 或 1 次,而不是 \=,這裡要非常小心! [實例] dg*
[...] 字元集合,表示中括號中所有字元中的其中一個。[^..] 這是上述 [...] 的補集,表非中括號內字元的其中一個。. 除換行字元外的任一單一字元。指本身,非指前所綁之字元。 就好像 shell 中的 ? 一樣。如果要指定真正的英文句點,要 用 \ 來 escape,就是說 \. 這時的 . 是代表真正句點,而不 是 regexp 中的特殊意義。其他如 \* 亦同。[實例]
^ 匹配行首,指其後綁住的字串,出現在行首才符合。$ 匹配行尾,指其前綁住的字串,出現在行尾才符合。含換行字元。不是在行首的 ^ 指的是 ^ 這個字元。不是在行尾的 $ 是指 $ 本身這個字元。 [實例] /^What
\(...\) 記憶 pattern,可由 \1, \2...\9 來叫出。[實例] :%s/\([a-z]\)\1/test/g
\< 匹配字(word)首。所謂 word 包括文數字及底線。\> 匹配字尾。這就是前所提及的限定用法,被 \<,或 \> 括住的 pattern 就會被限制住,使 regexp 不能再向右(左)擴充解釋。ed 及 perl 中可以 \b 來表示這兩個符號,perl 中只支援 \b,ed 則 \b 及 \<, \>皆支援。但在 perl 可多加個 ? 來限制 regexp 的擴充解譯。 功能上而言,這是和 ^ $ 一樣的定位樣式(anchor pattern)指所綁住的字串必須是單字邊界(word boundary),前或後或前後除了空白字元及標點符號外不可再有其它字元。 在 vim 中 \b 是表示 <BS> 即 backspace 鍵。 [實例] :%s/\<abbbc\>/test/g
\{n,m} 指前所綁住的字元或字元集合最少出現 n 次,最多出現 m 次。這在一般的 regexp 表示成 \{n,m\}。vim 及 elvis 兩種表示法皆支援。perl 則直接使用 {}。以下會舉四種不同的例子,請大家發揮一下想像力。:-) [實例] \{最小值,最大值}123 12 1 123456 1234567 12345678 1234 12345如果下 :%s/[0-9]\{3,4}/test/g 的話,那 1,12 這兩組不會被替換,因為不滿 3 位數。而 12345,則會換成 test5。123456,則會換成 test56。12345678,則會換成 testtest。1234567 也是會換成 testtest。123,1234 這兩組則會被替換成 test。您可以親自操作一次就知道怎麼一回事了。操作時最後加 gc 來 confirm,這樣您會更瞭解實際替換的內容。ㄟ,別忘了 u 可以回複您的編輯動作。
?
中介字元(metacharacter, or character classes)
主要是簡化 regexp 的書寫。
\s 表空白字元,即 <Space> 或 <Tab>。不含換行字元,這是編輯器的特性使然。在 perl 的 \s 是包含換行字元的。而且 vim 及 elvis 皆不支援 \n 這種換行中介字元。
\S 表非空白字元。\d 表數目字(digits),即 [0-9]。\D 表非數目字,即 [^0-9]。\w 表一般字元(word character),包括底線。即 [0-9a-zA-Z_]。\W 表非一般字元,即 [^0-9a-zA-Z_]。\a 表英文字母(alphabetic character),即 [a-zA-Z]。\A 表非英文字母,即 [^a-zA-Z]。\l 表小寫字母(lowercase character),即 [a-z]。\L 表非小寫字母,即 [^a-z]。\u 表大寫字母(uppercase),即 [A-Z]。\U 表非大寫字母,即 [^A-Z]。原始 vi 不支援此種中介字元。 使用中介字元的比對速度將會比使用字元集合 [] 的快。
?
全域性的指令
:[range]g/pattern/[cmd]
cmd 是 ed 可用的指令,預設是 p(print),您可查一下 man ed,就可以知道有什麼指令可用。這個小節裡主要是說明 d(delete) 的功能。因為是要說明如何消除空白行。需注意的是,d 是行刪除指令,凡含 pattern 的整行都會被刪掉,而且 range 不指定的話,預設是全篇文章,因為 g 就是代表 globe。
在 vim 的 help 檔裡說的是 ex 指令,但 ex 實際上是和 vim 連結的,因此這裡特別指出 ed。但 ed 的指令少數可能會和 vim 的 ex 不同,這是因為 ed 和 vim 並非同步在發展,作者也非同一人。:g/^$/d
這樣就會刪除全文的空白行。前面已提過 ^$ 代表的是空白行。但這裡有個問題,如果空白行裡包含了其它空白字元(即 Space 或 Tab)的話。表面看起來是和一般空白行一模一樣,但卻暗藏玄機,用上面的方法就無法刪除這種空白行了!怎麼辦?來!看招!
:g/^[<Space><Tab>]*$/d
在 vim 或 elvis 裡您可以如此照打,也就是 <Space> 代表空白字元,<Tab> 代表按 Tab 鍵的結果。在原始 vi 則不行,得自行按出特殊字元出來,就是 Ctrl-v Space 及 Ctrl-v Tab。或採更簡單的打法:
:g/^\s*$/d
還記得中介中元嗎?好用吧!少打了不少字。:-) 意思就是刪除含 0 或 1 個以上空白字元的行。
有些書中寫成 :%s/^$//g 可以刪除空白行,這是錯誤的,因為 :s 這個指令只更動一行裡的內容物,但不會做刪除一行的動作。
?
&替代變數
代表置換時合於 patern 的字元或字串。
:3,7s/.*/ &/但這樣連空白行也是會插入空白字元,較高明的做法是:
:3,7s/.\+/ &/這樣空白行就不會去動它了!想通了 .* 及 .\+ 的意思了嗎?往前翻一下 . * \+ 的定義。
:3,7s/^ //就是刪去行首的二個空白啦!
:%s/\<Edward\>/[&]/g
:%s/\<Edward\>/\U&/gㄟ!\U 不是代表非大寫字母嗎?喔!您搞錯位置了。\U 在 pattern 的位置的時候是指非大寫字母的樣式,即 [^A-Z],但如果是在置換字串位置的時候是指將其後的字串通通改成大寫。與其相對的是 \L,會將其後的字串改為小寫。詳細請 :h sub-replace-special。
:%s/.*/&<BR>/g
怎麼樣,是否已感覺到 regexp 威力無窮了呢?還是您已經快睡著了呢?:-) 不過也請您想想,如果是在沒有 regexp 功能的編輯器裡,範例中的一些動作您會怎麼做呢?一個一個去改?
?
greedy 陷阱
regexp 會有貪心的傾向,什麼意思呢?就是說在同一行內,如果有多個符合 pattern 的情形,會找最長的那一個。
注意!greedy 的特性是針對會反覆比對的 regexp 而言,例如:*, \=, \+, \{} 等。前面所舉的 .* 的例子,由於 greedy 的關係,在整篇文章中做替換時,會被當成是每一行整行,因為 regexp 會去找每一行最長符合的那一個。最後提醒您,這可不是 regexp 的全部,礙於篇幅及在下功力的問題,當然是沒辦法全面詳盡的向各位做介紹,在下只是將各位領進門,修行就得看各位了!如果還想更深入的研究 regexp,可參考: Mastering Regular Expressions(O'Reilly & Associates) 一書。