[VIM技巧]global命令詳解

來源:互聯網
上載者:User
發信人: vale (淺穀), 信區: VIM
標 題: global命令詳解
發信站: 水木社區 (Fri Jun 15 17:05:55 2007), 站內

global命令是Vim最強大的命令之一(個人認為是No.1),將其摸透用熟可以事半功倍,
本文總結了版上的一些經典問題,結合自己的使用和理解,試圖通過執行個體詳細介紹一下
其用法。樣本難度不一,有些並沒有多少實用性,為題而生,讀者各取所需吧。樣本說
明並不非常細緻,以免羅唆。每區段標頭下列出了所涉及的內容在Vim help中的位置,以
供尋找。文中用詞未必標準(我沒看過Vim中文協助),觀點也難免有錯,請大家指正。

|1.| global命令形式
|2.| global與substitute
|3.| global標誌的[range]用法
|4.| global與Vim指令碼
|5.| 小結

==============================================================================
*1.* global命令形式

:h :g
:h 12.4

:[range]global/{pattern}/{command}

global命令在[range]指定的文本範圍內(預設為整個檔案)尋找{pattern},然後對匹
配到的行執行命令{command},如果希望對沒匹配上的行執行命令,則使用global!或
vglobal命令。

先來看Vim使用者手冊裡的一個經典例子。

【例1】倒序檔案行(即unix下的tac命令)

:g/^/m 0

這條命令用行首標記/^/匹配檔案的所有行(這是尋找的一個常用技巧,如果用/./則是
匹配非空行,不滿足本例要求),然後用move命令依次將每行移到第一行(第0行的下一
行),從而實現了倒序功能。

global命令實際上是分成兩步執行:首先掃描[range]指定範圍內的所有行,給匹配{pa
ttern}的行打上標記;然後依次對打有標記的行執行{command}命令,如果被標記的行在
對之前匹配行的命令操作中被刪除、移動或合并,則其標記自動消失,而不對該行執行
{command}命令。{command}可以是一個ex命令,也可以是用|分隔的多個ex命令,這樣我
們就可以對被標記行,或從標記行定址到的行進行多種不同的操作。

標記的概念很重要,現以例說明。

【例2】刪除偶數行

:g/^/+1 d

這條命令也是匹配所有行,然後隔行刪除(其中+1用以定位於當前行的下一行)。為什
麼是隔行呢?因為在對第一行執行+1 d命令時刪除的是第二行,而第二行雖然也被標記
了,但已不存在了,因此不會執行刪除第三行的命令。

本例也可以用normal命令實現:

:%norm jdd

%指定整個檔案,然後依次執行普通模式下的jdd,即下移刪除一行。與global命令不同
之處在於,%norm是按照行號順序執行,在第一行時刪除了第二行,後面的所有行號都減
一,因此在第二行執行jdd時刪除的是原來的第四行。也就是說,global命令是通過偶數
行標記的消失實現的,而normal命令是通過後續行的自動前移實現的。

【例3】刪除奇數行

:g/^/d|m.

光是:g/^/d顯然不行,這會刪除所有行,我們需要用move命令把偶數行的標記去掉。當
然,本例可以很簡單的轉換成【例2】,在此只是用來強調標記的概念。

本例若想用normal命令實現比較有意思,%norm dd也同樣會刪除整個檔案,%norm jkdd
就可以,我不知道兩者為什麼不同,可能和normal命令內部的運行機制有關。

==============================================================================
*2.* global與substitute

:h 10.4
:helpg ms-word/c

不少vimmer覺得這兩個命令差不多,的確,它們的形式很相似,都是要進行尋找匹配,
只不過substitute執行的是替換而global執行的其它命令(當然,substitute預設的[r
ange]是當前行,這點也不同)。先看兩個例子,體會一下:s和:g不同的思維方式。

【例4】double所有行

:%s/.*/&/r&/
:g/^/t.

substitue是尋找任意行,然後替換為兩行夾斷行符號;global是將每一行複製(:t就是:co
py)到自己下面,更加清晰明了。

【例5】把以斷行符號排版、以空行分段的文本變成以斷行符號分段的文本

很多txt格式的ebook,以及像vim help這樣的文本,每行的字元數受限,段之間用空行
分隔。若把它們拷貝到word裡,那些硬斷行符號和空行就比較討厭了,雖然word裡也有自動
調整格式的功能,不過在Vim裡搞定更是小菜一碟。先看看用替換如何?。

:%s//n/n/@!/ /

/n/n/@!是尋找後面不跟斷行符號的斷行符號(關於/@!的用法請:h //@!,在此不多說了),然後
替換為空白格,也就是去掉用於排版的斷行符號。global命令則完全是另一種思路。

:g/./,/^$/j

/./標記非空行,/^$/尋找其後的空行,然後對二者之間的行進行合併作業。也許有人會
問,段中的每一行會不會都執行了j命令?前面已經說過,在之前操作中消失掉的標記行
不執行操作命令,在處理每段第一行時已經把段內的其餘行都合并了,所以每段只會執
行一次j命令。這條命令使用global標記做為[range]的起始行,這樣的用法後面還會詳
述。

如果想要保留段落間的空行呢?若用替換實現,需要尋找的是前後都沒有斷行符號的斷行符號:

:%s//n/@<!/n/n/@!/ /

這樣的Regex可能會令很多vimmer頭大了。而對於global命令則只需要將合并範圍
的結束位置上移一行就行了,由此可見global命令的方便之處。

:g/./,/^$/-1j

global經常與substitute組合使用,用前者定位滿足一定條件的行,用後者在這些行中
進行尋找替換。如:

【例6】將aaa替換成bbb,除非該行中有ccc或者ddd

:v/ccc/|ddd/s/aaa/bbb/g

【例7】將aaa替換成bbb,條件是該行中有ccc但不能有ddd

如何寫出一個匹配aaa並滿足行內有ccc但不能有ddd的Regex?我不知道。即便能寫
出來,也必定極其複雜。用global命令則並不困難:

:g/ccc/if getline('.') !~ 'ddd' | s/aaa/bbb/g

該命令首先標記匹配ccc的行,然後執行if命令(if也是ex命令!),getline函數取得
當前行,然後判斷是否匹配ddd,如果不匹配(!~的求值為true)則執行替換。要掌握這
樣的用法需要對ex命令、Vim函數和運算式有一定瞭解才行,實際上,這條命令已經是一
個快捷版的指令碼了。可能有人會想,把g和v連起來用不就行了麼,可惜global命令不支
持(恐怕也沒法支援)嵌套。

==============================================================================
*3.* global標誌的[range]用法

:h range

在global命令第一步中所設的標記,可以被用來為{command}命令設定各種形式的[rang
e]。在【例2】和【例5】中都已使用了這一技巧,靈活使用[range],是一項重要的基本
功。先看看【例2】和【例3】的一般化問題。

【例8】每n行中,刪除前/後m行(例如,每10行刪除前/後3行)

:g/^/d 3 | ,+6 m -1
:g/^/,+6 m -1 | +1 d 3

這兩個命令還是利用move來清除保留行的標誌,需要注意的是執行第二個命令時的當前
行是第一個命令定址並執行後的位置。再看兩個更實用點的例子。

【例9】提取條件編譯內容。例如,在一個多平台的C程式裡有大量的條件編譯代碼:

#ifdef WIN32
XXX1
XXX2
#endif
...
#ifdef WIN32
XXX3
XXX4
#else
YYY1
YYY2
#endif

現在用global命令把Win32平台下代碼提取出來,拷貝到檔案末:

:g/#ifdef WIN32/+1,/#else/|#endif/-1 t $

t命令的[range]是由逗號分隔,起始行是/#ifdef WIN32/標記行的下一行,結束行是一
個尋找定位,是在起始行後面出現的#endif或#else的上一行,t將二者間的內容複寫到
末尾。

【例10】提取上述C程式中的非Win32平台的代碼(YYY部分)

首先說明一下,這個例子比前例要複雜的多,主要涉及的是[range]的操作,已經和
global命令沒多少關係,大可不看。加到這的目的是把問題說完,供喜歡細摳的朋友參考。
本例的複雜性在於:首先,不能簡單的用#else和#endif定位,因為代碼中可能有其它的
條件編譯,我們必須要將尋找範圍限定在#ifdef WIN32的block中;另外,在block中可
能並沒有#else部分,這會給定位帶來很大麻煩。解決方案是:

:try | g/#ifdef WIN32//#else/+1, /#endif/-1 t $ | endtry

先不管try和endtry,只看中間的global部分:找到WIN32,再向後找到#else,將其下一
行作為[range]的起始行,然後從當前的游標(WIN32所在行,而非剛找到的#else的下一
行)向下找到#endif,將其上一行作為[range]的結束行,然後執行t命令。但對於沒有
#else的block,如第一段代碼,[range]的起始行是YYY1,而結束行是XXX2(因為尋找
#endif時是從第一行開始的,而不是從YYY1開始),這是一個非法的[range],會引起
exception,如果不放在try裡面global命令就會立刻停止。

與逗號(,)不同,如果[range]是用分號(;)分隔的,則會使得當前游標移至起始行,在查
找#endif時是從#else的下一行開始,就不會產生非法[range],用不著try,但帶來的問
題是:沒有#else的block會錯誤的把後面block中的#else部分找出來。

==============================================================================
*4.* global與Vim指令碼

:h script
:h expression

經常有人問:XxEditor有個什麼功能,Vim支援嗎?很可能不支援,因為Vim不大會為特
定使用者群提供非一般化的功能,但很少有什麼功能不能在Vim定製出來,如果是你常用,
就加到你的vimrc或者plugin裡。指令碼就是定製Vim的一種利器。本文不討論指令碼的編寫,
而是介紹如何實用global實作類別似指令碼的功能,實際上,就是利用命令提供的機制,做
一個簡化的指令碼。

【例11】計算檔案中數字列之和(或其它運算)

:let i=0
:g/^/let i+=str2nr(getline('.'))
:echo i

首先定義變數i並清零,然後用str2nr函數把當前行轉成數字累加到i中,注意Vim不支援
浮點數。global在這裡實際上是替代了指令碼裡的for迴圈。

Vim中最常見的一個問題是如何產生一列遞增數字,有很多解決辦法,調用外部命令,錄
宏,用substitute命令,還有專門的外掛程式,而用global命令,可以實現一些更進階的功
能。見下例。

【例12】給有效程式碼添加標號

在_Data Structures and Algorithm Analysis in C_一書中,作者為了便於討論,將代
碼中的有效行加上注釋標號,例如:

/* 1 */ unsigned int factorial( unsigned int n )
{
/* 2 */ if( n <= 1 )
/* 3 */ return 1;
/* 4 */ return( n * factorial(n-1) );
}

為了在添加標號後能對齊,我們預先在每行代碼前面插入足夠多的空格(這當然很簡單),
然後用global命令自動添加標號:

:let i=1 | g//a/s/ /{8}//=printf("//* %2d *//",i)/ | let i+=1

其中變數i用來記錄標號,g命令尋找有字母的行,然後把前8個空格替換成注釋標號,每
行處理完成後標號加一。替換中用到了//=,一個非常有用的功能。

最後我們再回到刪除特定行的例子,用變數來搞定。
【例13】每n行中,刪除前/後m行

:let n=10 | let m=3
:let i=-1 | g/^/let i+=1 | if i%n<m | d
:let i=-1 | g/^/let i+=1 | if i%n>(n-m-1) | d

我們用i來記錄處理行的位置,通過i的值與m和n的關係決定何時進行刪除操作,這樣的處
理方式比【例8】的方法要清晰明了很多,而且更加通用。

==============================================================================
*5.* 小結

要用好global命令並非易事,命令中的每一部分都值得仔細研究:只有掌握了range原理
,才能自如的在檔案中定位;只有精通pattern,才能有效匹配到想要找的行;只有熟
悉ex命令,才能選用最合適的功能進行操作;只有對變數、運算式、函數等內容有一定
瞭解,才更能讓global命令實現指令碼的功能。總之,global是一個非常好的架構,對Vim
越是熟悉,就越能將其種種武器架設在其上使用,發揮更大的威力。

global當然並非萬能,功能也有所欠缺,最主要的問題是只能用Regex來標誌匹配
行,如果能用任意運算式來標記(或者從另一個角度,如前mv版主runsnake所說,引入
求值Regex),則可實現更加方便功能。比如前述的幾個刪除特定行的問題,將會
有簡單而統一的解決方案。上述例子如果用sed、awk等專門的文本處理工具,或者perl
之類的script語言也非難事,有些實現起來會更加方便。本文提供的Vim解決方案未必簡
單,甚至可能是難於理解,目的在於介紹global的使用。對於那些不會或者不能使用其
它工具的朋友,參考價值可能更大一些。其實Vim的功能實在很豐富,值得我們深入學習。
少林七十二絕技固然高妙,會的越多自然功力越強,不過只要會上一門六脈神劍或小無
相功,也足以獨步江湖了。

==============================================================================
vim:tw=78:fo=tcq2:isk=!-~,^*,^/|,^/":ts=8:ft=help:norl:
※ 修改:·vale 於 Sep 27 12:26:18 修改本文·[FROM: 219.143.141.*]
※ 來源:·水木社區 newsmth.net·[FROM: 219.143.156.*]
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.