vim-logo.gif我們前面談的很多東西像搜尋「/」取代置換「:s」全域命令「:g」這些,功能都可以說是很強大,但如果配合上常規表示法(regular expression)就能發揮更大的效用,就像我們在談 vim 搜尋的時候有約略提到用 [0-9] 表示只出現一次的數字,而數字可以是 0 1 2 3 4 5 6 7 8 9 。這些都可以說是被規範在常規表示法裡面(regular expression)。或許你看到這個英文已經打開 vim 打 :h regular-expression 來看說明了。沒錯,這就是我們這次要談的在 vim 裡面的常規表示法。

基本上,我們不會討論學術上常規表示法的定義,我們只會談用 vim 配合上這種表示法(以及相關的特殊符號)能夠為我們做些什麼。所以我們可以輕鬆一點來討論這個話題。

在前面有關 vim 使用的討論中,我們學了像[0-9]、[A-Z]這樣的表示法,也學了\{nin,max\}、\{n} 指定出現次數這樣的方式,也有「\|」指定出現是以「或」(Or)運算來比對。可是或許你會問,如果我現在想要比對的字串本身就已經是像這樣:

[a-d][0-9]\{7,8\}

而且我還要找這樣的東西只連續出現三到五次的字串該怎麼辦?我該怎麼告訴 vim 要把這些東西組合(group)起來?如果你寫成像下面這樣是不能執行的,因為\{min,max\}不能直接連用

\([a-z]\{2}\)\{2,\}\{3,5\}

還記得我們可以用 \1,\2,…\9 ,來代替之前的\(搜尋字串\)嗎?沒錯,\(\)這個就可以幫我們把東西組合成一個單元(atom),所以今天我就可以把上面的東西寫成這樣:

\([a-d][0-9]\{7,8\}\)\{3,5\}

所以同理,不管是固定字串也好或是這類用常規表示的字串也好,你都可以用\(\)把他們組合起來。比方說你想要檢查是不是不小心把某些字串接在一起了,你就可以這樣寫:

\(Lemon\|Banana\|Apple\)\{2,\}

不過在 vim 要注意的是,在比對的時候 vim 會選擇最長的比對句子。比方說有一個句子長成這樣:

Lemon 3 Banana 2 L. Lemon 2 Apple 5 CD 3 D. Apple 4

而我下的指令是這樣:

/Lemon.*Apple

則它搜尋出來的區域會是這樣子:

Lemon 3 Banana 2 L. Lemon 2 Apple 5 CD 3 D. Apple 4

所以像前面用\(Lemon\|Banana\|Apple\)\{2,\}在一行裡面所搜出來的區域就會是最大的組合數的那個。

如果你希望找到的次數是在符合範圍內最小的那個,可以用\{-min,max\}。你可以注意到在 min 前面多了一個減號「-」。

不過日子有時候也不是那麼好過,比方說我想要找搜尋一個函數,可是我不確定在程式裡面用的是 func、funct 或是 function ?當然你可以寫成:

/\(func\|funct\|function\)

可是這樣做的問題是如果真的出現 func/funct/function 的話,可能就只會把 func 這部分搜尋出來(這是因為表示法的問題,因為我們把 func 放最前面,如果反過來就比較不會有問題),所以如果你想要的是把 func/funct/function 都變成 function 的話,可能就會出現問題。沒關係,你用的可是 vim 呢!你可以用下面這方法:

/fun\%[ction]

\%[ ] 表示的意思是說要照位置來比對,簡單地說如果要比對到 t 的話,那必須也要同時比對到 c,所以如果東西長成 functi 的話,那也是會被比對出來的。

剛剛和幾個朋友玩了一下動動腦遊戲,討論了幾個問題。我問如果我想要比對的是 aa, bb, cc,…,zz 的話怎麼做?布丁長輩回得很快,DK長輩也同時答出來,就是

\([a-z]\)\1

因為 \1 就是用來表示由\( \)夾起來的東西。

那如果我想要比對的除了是 aa, bb, cc, …, zz 之外,我還要連續出現兩次的,像是 aacc, bbdd 才要的話,又怎麼做呢?如果你按照上面的邏輯很快打出下面的指令, vim 會跟你說這樣的寫法有錯(Illegal back reference) :

\(\([a-z]\)\1\)\{2}

這是因為 \1 認到的是「 \([a-z]\)\1 」這一串,可是自己怎麼 reference 自己?所以就出現錯誤。所以你很快地發現自己的錯誤,剛剛那個\1因為外面又加了一層 \( \) ,也就是從左邊開始算,包住 [a-z] 已經是第二個 \( 了,所以你很快就改成:

\(\([a-z]\)\2\)\{2}

不過你可能又問了,如果我只是想要組合這些字串,並不是想要拿來當作後面 \1 \2 的指代,或者是我看到一個很漂亮的組合方式,卻不想破壞原來 \1 \2 … 編號的話怎麼辦? vim 也提供了只供作組合(group)的特殊符號,所以我們可以把剛剛那個有問題的 「 \(\([a-z]\)\1\)\{2} 」 拿來加一個符號就好了:

\%(\([a-z]\)\1\)\{2}

前面你看到「\|」這個「或」(OR)運算,你可能也會想那有沒有「且」(AND)運算?也就是說你想要找在同一行裡面同時會出現的多個搜尋字串,舉例來說,我想要找同一行裡面同時出現「黃連」和「阿膠」的字串,我可能的作法是先「/黃連.*阿膠」找一次之後,再用「/阿膠.*黃連」再找一次。假如我們想要用 :g 來讓這些東西同時一起出現的話難道不可能嗎? vim 提供了「\&」當作「且」運算。所以你要找「黃連」和「阿膠」同時出現在同一行的話,可以這樣用:

:g/.*黃連\&.*阿膠/

至於為什麼要用 .* 加在這兩者的前面,主要是受到 \& 的比對方式,你可以 :h \& 看到詳細的說明。

看到這裡,我想你應該也動手試試看。之前看一本書上面寫說為什麼要學用這些看起來很難用的工具呢?如果只是花人工一個個做像取代這樣的無聊且重複的工作,大概什麼人都沒有成就感。但是學了這些所謂看起來很難用的工具,像是 vim 等等,它可以讓這些工作像是一連串的小遊戲,而且當你完成之後你會佩服自己的聰明,而不是感覺到生命的空虛。我想,如果你的工作和文字編輯很有關係的話,或許更該裝一個 vim 來使用看看 ,它或許可以給你帶來更多的樂趣而不是枯燥無味的工作壓力。

PS. 雖然 vim 配合常規表示法很強大,但是常規表示法也有它所不能表示的,比方說我想要比對的是: ab, aabb, aaabbb, aaaabbbb, … 的話(有多少個 a 在前面就要有多少個 b 在後面),常規表示法是沒有辦法用的,你當然可以嘗試,不過這個已經在數學上證明是不可能的了 :p