Vim 正規表示式(1)

這個教學中,主要使用 vim 來示範簡單的正規表示式(regular expression),如果不懂可以看一下 wikipedia 上面的正規表示式。看不懂?沒關係,簡單的說就是用某些匹配字元(某些符號,例如"*", "|", "[, ]" 等來找到輸入文字的特徵。

舉實際的例子來看好了,假設我們要用 latex 來排版,需要加索引的時候,通常都會在要加索引的字後面加上 \index{keywords},例如:

Nulla ut vestibulum \index{vestibulum} dolor. Aenean aliquet nisl et quam 
suscipit, eget porta libero molestie. Fusce eget nisl ac urna maximus laoreet. 
Donec bibendum iaculis orci id tristique. Quisque nunc quam, ultrices id sapien 
et, hendrerit \index{hendrerit} pharetra tortor. Class aptent taciti sociosqu 
ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam ut massa 
nec enim pharetra varius. Aenean nec arcu id sem pretium congue id ut orci. 
Curabitur ultricies in augue ut dignissim. Nulla facilisi.

但是如果你是一份文件,懶得手動加入索引時(假設需要加入特徵的字有 65535 筆時,一個一個加,不就做到天荒地老?),像下面這個例子中,我想要把「Lorem ipsum 福祿壽」當成索引:

\name[1. ]{Lorem ipsum 福祿壽}
\name[2. ]{Quisque nunc 過新年}
\name[3. ]{Vestibulum sit 來爬山}
...
\name[65535. ]{Curabitur augue 千里馬}

用肉眼判斷,需要加索引值的字元都是放在大括號內({}),所以這就是特徵,如果我們要用 vim 要怎麼做呢?在介紹完整作法之前,先來介紹一下 vim 的編輯模式和指令模式。vim 在預設進入編輯器時,是指令模式,要進入編輯模式需要按 i (insert),要跳出編輯模式進入指令模式則是按 esc 鍵 + : (冒號)。而正規表示式則是需要在指令模式中下指令。下面一步步解釋如何做:

  1. 了解如何取代(為了方便解釋,在指令下方有 1,2,3, ... 數字來指示字元位置)

    :%s/A/B/g
    1 2 3 4 5 
    

    1 (:) 代表進入指令模式

    2 (%s) 代表是所有行號(%)取代(s)

    3 A 代表某個特定 pattern

    4 B 代表若符合 A pattern 時,取代為 B

    5 g 代表全域(也就是在整行中所有符合的匹配規則(pattern)都會被取代掉),若沒有標示 g ,則只會取代掉第一個

    例如要把下面一段文字(取自Flora of Taiwan)中的 "lanceolate" 取代成 "oblong":

    Culms slender, to 70 cm tall, 1.5 mm in diam. Blade to 15 cm long,
    (2-)5-15 mm wide; ligule chartaceous, minute, less than 1 mm long,
    truncate. Panicle open, to 30 cm long, branches 4-6-nate, verticillate.
    Spikelets 1-flowered, 3 mm long; glumes unequal, lower glume narrowly
    lanceolate, 1.8 mm long, chartaceous, 1-nerved; upper glume lanceolate,
    3-nerved, 2.5 mm long, chartaceous; lemma subcoriaceous, lanceolate,
    3-nerved, acute, 3 mm long; palea subcoriaceous, linear-lanceolate,
    acute, 2-keeled,as long as spikelet.

    vim 的指令如下:

    :%s/lanceolate/oblong/g

    輸入上述指令則會將所有 lanceolate 取代成 oblong

  2. 範圍
    如果要表示範圍,可以用中括號"[ ]"來包住要表示的範圍,通則可以這樣寫

    [n-m]

    其中 n 代表起始的值,m 代表結束的值,這些值可以是英數字,例如


    [1-9][0-9]

    上面這個例字代表符合10, 11, 12, ... , 99 的數字都會被抓出來,範圍也可以是用","(逗號)做分隔,例如

    [1,3-5]

    代表符合 1 或是 3, 4, 5 數字的匹配規則都會被找出來。如果是英文字的話,可以使用```[A-Z]```代表符合 A 到 Z 所有的字元都會被抓出來,大小寫是有區別的。也可以使用其他的正規表示式符號來搭配,例如"*"代表符合 0 到多個符合的所有字元等,但如果是要符合所有的字元包括空白行則是要用".*",通常是使用包在括號、引號的字元匹配規則內,細節請參見 vim regular expression 的文件。
    
    再來看一個例子,如果我要把上面 Flora of Taiwan 的文字中,有數字單位的,例如 10 cm, 1.5 mm 等的匹配規則 抓出來,如下:
    

    Culms slender, to 70 cm tall, 1.5 mm in diam. Blade to 15 cm long,
    (2-)5-15 mm wide; ligule chartaceous, minute, less than 1 mm long,
    truncate. Panicle open, to 30 cm long, branches 4-6-nate, verticillate.
    Spikelets 1-flowered, 3 mm long; glumes unequal, lower glume narrowly
    lanceolate, 1.8 mm long, chartaceous, 1-nerved; upper glume lanceolate,
    3-nerved, 2.5 mm long, chartaceous; lemma subcoriaceous, lanceolate,
    3-nerved, acute, 3 mm long; palea subcoriaceous, linear-lanceolate,
    acute, 2-keeled,as long as spikelet.

    這樣就可以使用下列的正規表示式來找匹配規則:


    [0-9,0-9.]*\ [c|m]m

    其中,第一部分的範圍有[0-9,0-9\.]*代表符合 0-9 的數字或是 0-9 開頭且後面有小數點("."在 vim 中有特別意義,所以保險起見用 \ 跳脫字元),後面接的 metacharacter 則是代表所有 0-9 以及 0-9 開頭後面有小數點的匹配規則都會被找出來。第二部分觀察到的匹配規則則是數字和單位之間有空格,所以我們用" "(空格)接在後面,第三部分則是單位,有 cm 及 mm,寫成範圍的正規表示式則是 [c|m]m (c 或是 m,後面接 m,則為 cm 或 mm)。但這樣寫會有個問題,就是如果文字中數字單位前有加範圍,例如 1-3 cm ,這樣就無法抓到,所以得另外再處理。

  3. 符合匹配規則(pattern)的分組
    有時候我們需要幾個不同匹配規則做不同的取代,那就可以用 grouping 的方式把匹配規則分組,分組是用\(分組的元素\)(一對括號,但前面要使用 backslash "\" 來跳脫字元),而符合前述匹配規則的分組,可以用 \ 加上 1~9 的數字來代表,如果 \0 表示符合所有匹配規則的比如:

    :%s/\(30\) \(cm\)/\1 \2/g
    

    \1 代表了符合 30 的字元

    \2 則代表符合 cm 的字元

所以我們在「Lorem ipsum 福祿壽」這個例子中要怎麼寫 vim 的正規表示式呢?第一,先找出匹配規則,這個匹配規則是可以「精確」描述出來的,這是最重要的步驟;第二,試著用搜尋(指令為 /)來試試看第一步中所找出的匹配規則,如果不對的話,再重複第一步驟修正,直到確定找出為止。

我們希望 Lorem ipsum 是一組詞,用空白分隔,加入 \index 變成\index{Lorem ipsum},而「福祿壽」是第二組詞,加入 \index 變成\index{福祿壽},第一組的 pattern 就是第一個字首字大寫,第二個字的字首小寫。第一組和第二組都被大括號包住,而且大括號前有\name[數字加上"." ],因此用 vim 寫符合上述規則的正規表示式如下:

:\\name\[.*\]{\([A-Z].*\) \([a-z].*\) \(.*\)}

 |--1-|--2--|----3------|4|----5----|6|--7--|

解釋:
第一部分(|---1---|)代表符合有\name的字元,因為"\"已是跳脫字元,所以要多加一個"\"

第二部分則是代表[ ](中括號)包起來的字元

第三部分則是代表Lorem ipsum中的第一個字Lorem,首字大寫,所以使用[A-Z].*,因為我們希望它在 index 時有階層的索引(請參見 latex 詳細說明),所以要特別處理。所以把它用\(\)當成第一個群組

第四、六部分是空格

第五部分則是Lorem ipsum中的第二個字ipsum,因此寫成[a-z].*,是第二個群組

第七部分則是最後「福祿壽」

接下來則是把符合的匹配規則取代成\name[1. ]{Lorem ipsum 福祿壽} \index{Lorem!ipsum} \index{福祿壽},vim 的語法如下:

:%s/\\name\[.*\]{\([A-Z].*\) \([a-z].*\) \(.*\)}/\0 \\index{\1!\2} \\index{\3}/g
   |-----------------------1--------------------|--------------2--------------|

解釋:
取代的部份(|---2---|)中,\0代表符合前述匹配規則的字元,\1 則是「Lorem」,\2則是「ipsum」,而\3代表「福祿壽」。

雖然正規表示式寫起來像火星文,但是只要能夠精確找到匹配規則的話,可以很快速的節省手動取代或修改的時間,多練習就會上手了!有興趣學習 vim 的朋友們可以參考李果正前輩寫的「大家來學 vim」

參考文獻及延伸閱讀:
1. vim regexp
2. 大家來學 vim

Comments

comments powered by Disqus