Thu 8 Sep 2005
當你對 vim 瞭解越多的時候,有時候你會覺得自己變得很貪婪,常常會問:如果 vim 連這個都做得到,那它可不可以怎麼怎麼?就像我們之前介紹的置換功能,你可能會覺得 vim 真的可以幫你做到很多事情。但是它能不能做更多事情?
舉例來說,有時候一篇文章裡面你可能不想要它的空白行,但是你用「:s」的話都是以一行一行當作單位,沒有辦法讓你砍掉這些空白行。雖然說你其實可以把換行符號取代掉,讓文章變成一行,然後再做兩三個取代命令來達成。但是萬一遇到要把包含某個字串的那行搬移或是刪除的時候又該怎麼辦呢?那這就是今天要講的「 :g 」全域指令。
我們先來看前面說的怎麼用 :g 砍掉空白行:
:g/^[ \t]*$/d
\t 是用來代表<Tab>鍵,:g 後面跟著的就是要尋找的字串,而最後就是跟著命令,像上述命令中的 d 就表示刪除該行的意思。所以套同樣的方式,我們也可以砍掉包含某特殊字元的行數。
不過你可能會想,難道 :g 就這樣了嗎?
當然不是!
還記得我們在做 search 的時候嗎?有時候連我都會覺得:拜託, vim 難道就不能把所有比對的地方一次給我看就好了?我為什麼還要一個個按 n n n n … 好麻煩。或是我能不能把不包含某個字串的行列出來呢?在原來講 vim 的 search 指令裡面,我們能做的就是針對想要的字串作比對,但是對於上述的要求,我們就必須透過 :g 來完成。
比方說在一篇文章中,我只想找有「小柴胡湯」的條文,可是用「/小柴胡湯」只能出現像下面的結果:

我還是會看到其他不包含「小柴胡湯」條文,但是用 :g 就不一樣了。假如我用這樣的指令:
:g/小柴胡湯/
就會看到變成這個樣子:

那如果我還想看到含比對字串的行數的話,就可以下:
:g/小柴胡湯/nu
那就會看到變成這樣子:

黃色部分就代表包含的行數。
那如果你想看到附近的條文的話,也可以用:
:g/小柴胡湯/z#=4
這樣就是只看比對的附近四條條文並用虛線標記起來。(你可以用 :h :z 看到詳細的說明)
那這時候也許你還會想:那我可不可以找不包含某字串的條文呢? vim 用 :g 也很容易可以做到。比方說我想要找不包含[湯散丸]的條文,也就是說你只想看到只有出證而沒有出方的條文:
:g!/[湯散丸]/
驚嘆號「!」就代表不包含後面的條文。
又假如一份文件長成這樣(像信件可能就會長成這樣):

那我想要把 From: xxx@blah.blah.com 的內容 content second 都砍掉的話怎麼辦?當然你可以用之前的選擇模式的方式,用上下左右或是用滑鼠把它砍掉,但是有了 :g ,你何必作這麼麻煩的事情呢?
我們要先把游標放在第二次出現的 From: xxx@blah.blah.com 上,然後用「 .,」先指定範圍,如果沒有這個範圍指定的話,那連第一個 content… first 都會被清掉。指令下成像下面這個樣子:
:.,g/xxx@blah.blah.com/;/yyy@blah2.blah2.com/-1d
yyy@blah2.blah2.com後面跟上的「/-1」表示是到 yyy@blah2.blah2.com 的上一行,如果沒用 -1 的話,那連 yyy@blah2.blah2.com 這行都會因為其後跟上的命令「d」而被砍掉。同理,+n 就表示到比對到的那行(本例是 yyy@blah2.blah2.com 這行)之後,還要再往下 n 行才執行後面的命令。上面的指令我們可以把格式寫成這樣:
:{range1}g/pattern1/{offset1};/pattern2/{offset2}{command}
這樣就是說你可以指定範圍{range1}作 :g ,而在此範圍下,又只作 /pattern1/ 和 /pattern2/ 之間的範圍(包含 pattern1 和 pattern2)加減後面的範圍{offset1}及{offset2},然後在這個已經被比對出來的區域中執行後面的指令{command}。

所以做完那個指令之後,原來的文件內容就會變成:

那你可能又想了(誰叫我們總是會想猜 vim 到底可以做到什麼程度?),那我是不是也可以指定區域範圍之後,並同時搬移到某個我指定的地方去?
當然可以!
比方說拿原來的信件範例來說,我可不可以把從 xxx@blah.blah.com 的信件放到文件的最後面去?我們可以下這樣的指令:
g/^From: xxx@blah.blah.com/;/^From:/-1move $
所以本來長成這樣:

執行之後就會變成:

所以我們可以看到它就真的按照我們想要的跑到文件的最後面「$」去了。而如果你想要的指令不是 move (搬移)的話,你也可以用 copy (複製)的方式。指令就是把剛剛的 -1move 改成 -1copy 就行啦。
不過如果你只是想要讓包含某字串的行搬到另外包含一個字串的話,上面這些可能又不能滿足你的需要了。
舉例來說像我今天把 BBS 的文章寄回去給自己,可是讀信的時候,它只會按照寄信的時間而不是根據 BBS 的文章 — 時間: 來排序。因為對信件來說,那都是算內文了。
如果我想要按照 BBS post 的順序來排序,難道就只好一篇篇改嗎?還是自己要寫一個判讀信件內容的程式呢?
:g/時間: /m?From .(email address) .*200.
這告訴 vim 把內文當中含有「時間: 」這東西移到往上尋找看到的第一個From .(email address) .*200. 這行底下
m?
m 就是指 move 的意思,「?」表示要 match 後面的才可以。
然後我們把時間拿掉:
:%s/時間: //g
把一封信件的最開頭那行的最後面有關時間的部分砍掉:
:%s/\(^From: .*tw\) .*/\1/g
告訴 vim 說把 From: 的底下那行連接起來 j 表示連接, 1 表示 1 行:
:g/^From: /j1
好啦,這樣你就把內文的時間移到信件的格式裡面去了。輕輕鬆鬆。
上面這些都是我個人自己會用到的,你可以用 vim 輸入 :h :g 看到相關的說明。
為什麼我會喜歡用 vim 呢?因為我怎麼想它就怎麼做,很直覺。不過,如果你不是拿 vim 當作你日常的編輯器的話,其實指令看來看去還是會忘記的。快裝一個 vim 來玩玩看吧!
Updated 2005-09-15:
今天在做區域的一些置換的時候,我用了像下面這樣的指令:
:g/^<s.*$/+1;/^\%(([0-9]\+)\|<u\|<s\).*$/s/^\%(([0-9]\+)\|<u\|<s\).*$/<\/ul>&/g
你可以看到在 :g/pattern1/ 後面跟了 /+1;。注意那個分號「;」,在我原來的文章裡面是用逗點「,」,我後來發現如果你不給{offset1}的話,也就是說不調整比對的起始行的話,用逗點「,」或是分號「;」在我今天測試的情況是沒有差別的。但是如果你會改變起使的比對位置的話,也就是用到{offset1}的話,你就必須使用分號「;」。所以在文章裡面我已經做了修正,把原來用逗點的地方一律改成分號「;」。
September 8th, 2005 at 6:15 pm
vim指令好像很難喔…
真的要多用才會用,
我英文水平又很低啦~>”"
September 8th, 2005 at 7:11 pm
網路上有李果正先生的大家來學 vim 的中文說明,值得一看。或是參考拙文的「不是打vi的廣告」系列,從第一篇 淺談 vim 開始看起。至於英文水平,這應該不成問題,因為我也很差 /_\,只是愛用 vim ,多看多用試出來的。如果在下的這些文章有哪裡寫得不周全不清楚的地方,還請不吝指教,用力給在下建議。 :p
September 8th, 2005 at 7:22 pm
寫得很好啦^^”
你的文章令我學得很快,
而且很用心,
我要把這裡設成書籤^^”
還要多謝你給的網址~
September 8th, 2005 at 7:25 pm
我第一篇回應打了6-7行
為什麼我只看到3行@@”奇怪…?
September 8th, 2005 at 7:30 pm
可能是因為用了<>這兩個符號,而忘了用 <>來代替,或是沒打好等等都有可能… 這種事情… 呃,其實我也還沒找到好方法解決…. 真是抱歉,還請多多包涵
September 8th, 2005 at 8:52 pm
不要緊^^”
我會繼續追看下去的,
加油喔^^”
September 8th, 2005 at 10:13 pm
[…] 紅塵一隅間拾得 「一命二運三風水,四積陰德五讀書」前者難以改變,但後者可以努力 « 不是打vi的廣告, vim 的全域指令「:g」(global)| […]
September 13th, 2005 at 12:09 am
[…] 我們前面談的很多東西像搜尋「/」、取代置換「: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 配合上這種表示法(以及相關的特殊符號)能夠為我們做些什麼。所以我們可以輕鬆一點來討論這個話題。 […]
December 17th, 2005 at 3:43 pm
December 25th, 2005 at 2:59 am
不错不错,这一篇的 :g 命令有很多用法我都不知道,日有所得,开心ing……
谢谢
January 10th, 2006 at 3:02 pm
November 17th, 2006 at 3:21 am
[…] 當你指定 “[a-z] 暫存器來使用的時候,事實上是把指定的內容覆蓋上去。但是如果你想要的是附加而不是覆蓋呢?那就得使用 “[A-Z] 了。比方說,我們之前談到 :g 的時候,有提到怎麼樣只顯示出包含搜尋字串的行。可是如果你想要把這些東西放進去暫存器留待下次使用的話怎麼辦? […]
July 25th, 2007 at 1:24 pm