Vi/Ex編輯器教程[3]

helloxchen發表於2010-10-22

第三章 功能強大的全域性命令

你可能會因為我在上一篇的教程中沒提到“:global”而覺得奇怪,不過“:global”其實不是一個地址。這事實上是一個行模式命令――全域性命令,並且它的作用比大多數使用者想像的要大得多。

就算是有經驗的使用者在想到全域性命令時也會與下面的這些話聯絡在一起:“如果你輸入了:global接著輸入搜尋式樣,然後是行模式命令,把它們放在同一行。那麼編輯器會對逐一對檔案中包含匹配式樣的行執行行模式命令”。也就是,在輸入:

     global /^Chapter [1-9]/ delete

後,使用者想到的是編輯器會查詢並刪除檔案中所有以“Chapter ”1到9開頭的行。沒錯上面的例子乾的正是這事,這個命令的這種用法隨處可見。但還是不時地出現誤用,以下命令:

     global /^Chapter [1-9]/ write >> t.of.contents

就算是有一定經驗的使用者也可能會以為上面命令的作用是將匹配式樣的行新增到名為“t.of.contents”檔案中去,這當然是錯的。(上面的命令更像是用來消耗磁碟空間的)




全域性命令的操作細節

更重要的是,對全域性命令的不瞭解讓使用者只能發掘到該命令一小部分的潛力,無法發揮它真正的作用。慶幸地是你再也不用受這種不瞭解的束縛了――在本文裡我們會完整地呈現這個命令的方方面面。

在指定的位置搜尋:

與其他的行模式命令一樣,全域性命令之前也可以放一至兩個地址。它的預設搜尋範圍是整篇文件,但如果你使用命令“257 , 382 global ….”時搜尋會從257行開始一直搜尋到382行(包括382行)。全域性命令前可以放置所有型別的行模式地址,因此以“?^Exercises? +++ , $ global”開始一個命令時編輯器會回搜到的第一個以“Exercises”開頭的行並以該行之下的第3行為全域性命令作用範圍的開端,這個範圍一直到檔案的最後一行結束。

標識匹配或不匹配的行:

輸入全域性命令“global”或“g”會使之對搜尋範圍內的每一行包含要搜尋的式樣的行進行標識。但輸入“global!”、“g!”或“v”的作用則相反:現在它只標識未包含搜尋式樣的行了。如果你正編輯一個記錄錯誤資訊的日誌檔案,你需要的只是以“Error 3b:”開始行,那可以用以下命令將其他行刪除:

     global! /^Error 3b:/ delete

選擇你自己的搜尋式樣分隔符:

因為這個命令總是由上至下對整篇文件(或是你選擇的範圍)進行搜尋,幾乎所有的標點符號都能用來表示搜尋式樣的開始與結束。也不需要用“?”或“/”來選擇搜尋方向。如果你想要刪除所有包含三個斜槓的行,這些命令:

          global +///+ delete
          global ;///; delete
          global ]///] delete
     

都比使用斜槓作為分隔符然後為欲搜尋的三個斜槓前分別加上反斜槓要來得簡單。(但是使用“!”作為分隔符要當心,因為全域性命令“:global”會把命令後的“!”當成指示全域性命令搜尋不含指定式樣的行的一個開關符號。)

當然這隻對緊跟在全域性命令後的搜尋式樣有效,這個搜尋式樣用來指定要標識的行。如果是在全域性命令前的用來指定全域性命令的作用範圍的搜尋式樣,那麼還得像往常一樣使用“?”和“/”作為分隔符。

看似無用的一些全域性命令:

有些時候,雖然只在檔案中找一行,使用“:global”或“:global !”也是明智之選。當選擇性的對行執行行模式命令時這是一種基本的技能。舉個簡單的例子,當你想要刪除最後一行當且僅當最後一行是空白行時。你可以選擇每次都自己移動到檔案末尾然後看一下有沒有附加的空行,但讓編輯器去檢查並刪除(如果需要刪除的話)會輕鬆一點,這時你可以輸入:

          $ global /^$/ delete
     

讓全域性命令標識你自己限定的範圍內的所有行也是有實用價值的技巧!我們說過的程式設計師――小何(Hal,在這系列教程的第一章中出場過的)在倒置檔案中的所有行時就使用過這一技巧。他所用的命令以完整的形式寫出來就是:

          global /^/ move 0
     

標識所有有“開端”的行,其實就是標識所有行(包括空行)。接下來該命令從第一行開始並將第一行移到虛構的行――零行之下。然後它再到第二行並同樣把第二行移到零行之下,原來的第一行這時成了第二行。它對第三行做同樣的動作,然後第四行,第五……。就這樣它逐漸地將行的次序顛倒了。

一個全域性命令後能使用多個命令:

可以在“:global”命令後使用多個命令和命令所用的搜尋式樣。在標識了特定行後,全域性命令接著逐個對標識行依次(依照你輸入它們的次序)執行所有命令。這些命令透過豎線(“|”)分隔。如果你輸入了:

          global /^CHAPTER/ substitute /APTER/apter/ | copy $
     

編輯器會對以章名(CHAPTER)開頭的每一行執行替換,將“CHAPTER”換成“Chapter”,然後複製該行(現在是以“Chapter”而非“CHAPTER”開頭了)到檔案末尾。這兩條命令(替換substitute和複製copy)的次序很重要,替換的命令必須在前,不然複製到檔案末尾的就成了原來的全部大寫的“CHAPTER”了。

並沒有限制說只能在全域性命令後使用兩條命令。對命令數目的上限並沒有規定。對整條命令序列(與全域性命令一起使用的多個行模式命令組成的序列)的長度的限制則依Vi版本的不同而有所不同。話說回來我從沒遇到過這一長度上限小於256個字元的。但對於命令序列的使用方式有些規定:

  • 關鍵字“global”和後面命令序列必須在同一行中。(指的是“物理行”,中間沒有回車符分隔的行,如果該行長度超過了顯示裝置的寬度的話,可以折行顯示――當然這沒關係。)
  • 命令序列不能包括“undo”(撤消)命令和另一個“:global”命令
  • 如果序列中包括一條在shell(命令直譯器)執行的命令,那它必須是序列中的最後一個命令(在同一命令序列中使用兩個或以上的sell命令是無意義的)。這樣該命令才能在它的shell命令列中使用管道操作(“|”,與命令序列的分隔符是一樣的),而不至於讓編輯器分不清那是一個全域性命令的分隔符還是一個命令列下的管道運算子。

命令並非一定要在所有全域性命令標識的行上執行:

使用全域性命令就如同用手工移至標識行上再執行命令一般。正如同有時你執行命令並不是對輸入命令時所在行進行操作一般,全域性命令中的命令序列也不一定要對所有標識過的行進行操作。這裡有三點要強調的:

  1. 在全域性命令後的任何命令都可以有自己的地址,與那些命令在單獨使用時一樣。因此這一命令串:
                   global /^XX/ - copy $ | /ZZ$/ , +5 delete
              

    是完全合法的。它逐個地找到以兩個大寫X開頭的行,上移一行,複製該行到檔案末尾,然後向下搜尋以“ZZ”結尾的行,並將該行及其下的五行刪除。

  2. 在全域性命令的命令串中即使你沒有給出命令的地址,這些命令命令還是可能不會對全域性命令標識的行進行操作因為它有預設的地址。這就是在本章的介紹部分中說的所說的全域性命令的錯誤用法。因為寫入(write)命令的預設地址範圍是整篇文件,所以那條命令的作用是當每遇到一條全域性命令標識的行就將整篇文件寫進(新增到)另一個檔案的末尾一次。要將全域性命令標識的行寫進另一個文件的正確做法是:
                   global /^Chapter [1-9]/ . write >> t.of.contents
              

    在寫入命令前面的“點”(半形句號)告訴寫入命令只對它所在的行進行操作。

  3. 但全域性命令後的命令序列中使用有預設地址的命令時,如果該命令不是序列中的第一個命令(即跟在“:global之後的命令”)的地址即使它有預設的地址,在沒有給它自己的地址的情況下它也可能並不是用全域性命令標識的行作為它的地址。原因:在全域性命令的命令序列中每一條命令都以上一條命令結束時的所在行為當前行。

在我的之前的一個例子中,講了如何將以“CHAPTER”開頭的行改為“Chapter”並複製到檔案末尾。那個任務本身很簡單因為被複制的行都是已經被更改了大小寫的行。那麼如果希望檔案中間的“CHAPTER”改為“Chapter”,而複製到檔案末尾的仍是全部大寫字母的形式時要怎麼做呢?這看上去似乎只要對調一下命令序列中的兩個命令的位置就行了,這樣就可以先執行復制命令然後再執行用來更改大小寫的替換命令,就像這樣:

          global /^CHAPTER/ copy $ | substitute /APTER/apter/
     

出乎很多人意料的是,這命令執行的結果與預想正好相反。這條命令會將複製到檔案末尾的行的大小寫更改而檔案中的那些行的卻仍然是全部大寫的。究其原因,複製操作將行復制到檔案末尾並在那裡――而不是原來由全域性命令標識的行結束複製操作。而替換命令以上一命令結束時所在的行(最後一行)為當前行。在沒有明確給出地址的情況下替換命令的預設地址便是當前行,因而它會對檔案末尾的行(複製的行)而不是原來的行(全域性命令標識的行)進行大小寫更改的操作。

但有一件事是無論在命令序列中的“當前行”怎樣地變化也不能改變的。當全域性命令從一標識行開始執行命令時,不管命令序列最後在哪一行結束執行(這一行成了新的當前行),全域性命令總是到下一標識行(而不是當前行)重新開始執行命令序列。要阻止全域性命令從下一標識行開始執行,只能讓命令序列中的命令刪除下一標識行――但那樣的話,全域性命令就會移到下一未被刪除的行開始命令。

假設你想要對檔案進行一些刪減――每隔一行就刪除一行。你可以用下面的命令:

          global /^/ + delete
     

全域性命令首先對每一行進行標識。當它來到行1時,它就執行命令刪除行2。然後移到下一未刪除行――行3,並刪除行4,依此類推。或者如果你想要刪除每三行中的後兩行,輸入:

          global /^/ + , ++ delete
     





全域性命令的例子

上面的那些例子除了用來說明全域性命令的工作原理外,還展現了它較鮮為人知的一些技巧。但上面的例子無法涵蓋它所有的重要技巧。這而補充一些有價值的技巧。

計數。有時全域性命令後面的命令序列與全域性命令標識的行根本毫無關連,這些命令並不對全域性命令標識的行進行操作。這種情況通常出現在我們需要重複執行一個行模式命令一定的次數時。

我經常受邀到一些展會上測試系統,就在展臺上測試。我不能總帶著一份有著10000行的測試檔案,因為我事先不知那個系統對儲存介質、格式有什麼樣的要求。我的做法當場新建一個檔案並在其中輸入10行,然後讓編輯器複製全部並貼到檔案末尾,如此反覆十次。(每次複製都使檔案的大小翻倍,所以最後檔案中就有10240行了。)

但那要求精確計算操作次數。如果我輸入命令的次數出現的錯誤(就算是在前後一次的誤差範圍內)那我要麼要得到一個只有我預想的一半大小的檔案,要麼有兩倍大――這將毀了整個測試結果。但我不打算自己數操作次數,我讓編輯器來替我數。在輸完開始的十行後,我給編輯器一條命令:

     global /^/ % copy $

這條命令讓編輯器通搜整篇文件,標識所有有“開端”的行(也就是所有行),然後對標識的十行逐一執行通篇複製的命令。這確保了命令會精確地執行十次。

注意這個技巧僅限於檔案中的行數與要命令重複執行的次數一致時使用。如果我在檔案中輸入了20行,要將之通篇複製10次可以這樣使用全域性命令:

     1 , 10 global /^/ % copy $

自動移動。你可能不時地會需要應付檔案中一系列的編輯問題,在沒使用全域性命令時你得一個一個地解決這些問題。但在這進行操作的點之間移動是件煩瑣的事。如果有一個式樣來找到這些需要編輯的點,或者你能寫出一個指令碼來將式樣插入到這些地方的話――就你第一章中小何做的那樣,那全域性命令能自動帶你到這些點。

你可能還記得小何用了一個指令碼來對原始碼進行標識,他將每行lint警告放在原始檔中相應的行後,中間用“XXX”分隔以利於定位這些行。假如這個“無惡不做”的資訊系統副主管又回過頭來要小何仔細看一下這些行,看是否能透過重寫這些行來消除“警告”作為補救的一種手段。

小何應該怎麼做呢?要快速的翻閱這些程式碼,然後逐個找出那些用來識別問題行的“XXX”式樣嗎?小何知道他所面對是義大利麵條式的程式碼,實際出現問題的地方與lint所指示的那些行可能會有的出入。在查詢問題點的過程中他可能已經跳過了幾個“XXX”式樣,因而在檔案中查詢下一個“XXX”可能會將他帶回他已經處理過的點,或者是漏掉了那些他在查詢實際的問題點時跳過的幾個“XXX”式樣。此外,他在修復一個問題時頻繁地使用式樣搜尋,這使得他無法在可視模式中使用“n”命令來快速地定位下一個“XXX”式樣――他必須每次都重新輸入式樣。

但是小何知道怎麼應付這些問題――回到行模式(在可視模式下輸入大寫的“Q”)然後使用一條簡單的全域性命令:

     global /XXX/ visual | write

這條命令讓小何先回到“XXX”在檔案中第一次出現時的所在的行,然後切換到可視模式等待小何編輯。在小何做完修改後,只要再輸入大寫的“Q”編輯器就會帶他到第二個包含“XXX”的行,並回到可視模式。不論在前一次編輯中小何怎樣地移動或進行何種操作,編輯器都能將他帶到第二個包含“XXX的行”。然後小何只要在每次進行完修改後按“Q”就能到下一個包含“XXX”的行中。在每完成一次編輯後write命令會自動地將修改過的檔案儲存到磁碟上。




現在換你了

在你將全域性命令的進階技巧在實際工作中運用前,這兒有一些練習來讓你練練手。我已經為每個練習提供了至少一種參考答案(見附錄),還有一個提示是針對最後一個問題(也是最難的一個)的。

複製後改為小寫。回想一下那個想把文中所有以“CHAPTER”開頭的行復制到檔案末尾的使用者。除了複製外他還想把文中原來的那些行中的“CHAPTER”改為“Chapter”――但複製到檔案末尾的那些行仍保留大寫的狀態。

現在我們已經知道下面的兩條命令都不能完成這項任務了:

     global /^CHAPTER/ substitute /APTER/apter/ | copy $
     global /^CHAPTER/ copy $ | substitute /APTER/apter/

怎樣才能用全域性命令(:global)完成這項任務呢?有許多種辦法,要找到一條不算太難。

準確的字串長度。一位老友在用troff1時做一些特殊的工作時需要在一行中插入連續的64個反斜槓。反斜槓的數目必須準確的為64個,不然troff不能正常地完成工作。在不費力數數的前提下,他怎樣才能準確地輸入64個反斜槓呢?

假設他要在第217行的字串“n(PDu”前面插入16個反斜槓。使用什麼命令才能完成這一任務而免於手工數數的尷尬呢?如果知道要用哪些命令的話那答案就呼之欲出了。

為段落編號。一位文件的作者將每個章節又分成了多個段落。他是一個troff的使用者所以他用將宏“.pp”單獨置於一行作為段落的標記。所以段落間的是以這種方式分隔的:

     which is the only way that argon gas can be dissolved
     in this liquid.
	
     .pp
     The problem of energizing the
      argon to fluorescence while
     it is dissolved was first approached by applying a strong

這個技術作家要怎樣用vi編輯器來為章節中的每一個段落編號呢?(你可能覺得這個問題有些“扯”,但就曾有一個Unix專家在電話中問我這個問題。)為了簡化問題我們假設每個章節中的段落數不超過35,並且編號用羅馬數字來表示。

這個問題還是有一定難度的,所以我要提供兩個提示。第一個當然是使用全域性命令了。在你已經準備放棄或要看答案時,可以先看一下第二個提示。




下一篇

在這篇教程的下一部分中,我會講一些其他用來處理文字和檔案的行模式命令,並會涉及到這些命令較不為人知的一面。如果你被本文中的全域性命令的內容壓得透不過氣來的話,那告訴你一個好訊息:替換命令要簡單得多,事實上其他的命令都要比全域性命令來得簡單得多。

而在講完了那些個命令後,這篇教程的後面的部分將會集中在可視模式上――比行模式要簡單也更有趣。



Appendix A 答案和提示

答案用uuencode編碼過。

begin 644 viex3_ans
M5FDO17BQX+RMQO@K2Z]7?H[IH<3`P92`*"@JAL+BTUL:Z[C$SJK0H="TH;'.
MRLSB"@J]SZJUK&PU[7$L.RWJ,K'H[H*"F=L;V)A;"`O7D-(05!415(O(&UA
MZQ:.HT-M87)KP_S![J.IHZS(N[KSO:O6
MKKBTUL:UO]J[3ST+37UL2XN,3.JM"AT+37UL2XH:,*"KNYT]#2
MN];6LKO$Q.TUK&]V,'+M;&UJVLT?G3T-"GM<2WO;>HOLW*Q_(O:NAL$-(
M05!415*AL+0J=#0M;W.Q+S^Q*G.LJ.LU^ZZ]/#S.:[N/P>Z]J[VKM;''L-#0M<2A
ML$-H87!T97*AL=39M,[,YKN[SJJT]"TM<2AL$-(05!415*AL:&C"@H*"G1R
M;V9FSLK,X@H*P_S![M#0H[H*"C$@+"`Q-B!G;&]B86P@+UXO(#(Q-R!S=6)S
M=&ET=71E("]N*%!$=2]<7&XH4$1U+PH*S:BY_=3+T-`Q-K3.M<3,YKN[P_S!
M[L"TS>JSR;C#R,[.:&CP[_2N[3.S.:[N[:U-JAL&XH4$1UH;''L++ER.O2
MN[CVM[30L;C<.cj msjjwm-="" m="">HT.C2JM*[N/;6
MT+SDLKW6Z*&JH:K4VL._N/:ZZK7$NO/#YLSMO-.T]"TU];$N*&P2:&QH:.V
M^-3:P[^X]KKJNO/7UL2XH;!)H;&UQ+CVROV^SX]K;.PN2UQ+'@NL6A
MH]*ROLW*Q]3:RK7*J*X]M;0O.2RO=;HNO.VSL+DO[3)SBEOLW/=7B
MT?FCN@H*+G!P24E)24D*"@H*L>"ZQ[`M,WJLG5XM*[LMG7]Z&CM=K2NSUH[H*"F=L;V)A;"`O
M7EPNK*MM#0HZRVU+/]
MM=K2NZ&BMOZX]K'JRK;0T,WBM<3+^=/0H;`NPNVCJ+ORR[72N];6NMRAL-2M
MRKRAL;7$HZG*_=?6SJJVSL+DL>"ZQJN[O.JKCSJJYYK>VM<30SLJ]H[H*"F=L;V)A;"`O7EPN"KW*OL_"TKO0T+7$P_S![LK'O=/0^-3:
MU>+2N]#0NO.AHZ.I"@K2JM:JMU
MQ*.LOF_M-*[S*AL+7:,3FVSJ&QM<3'Z;_VH://PL/FM<3+Q-#0L>W*OK7$
MM]:Q,K'NNK4VLSFN[O#_,'NU,O0T,>PHZRZS;W3S+`M+7$R/VX]LSFN[O#
M_,'NUO"X]M:TT-"Z[7$L>2[K?IO_:CN@H*+G!P24E)24E)2
轉自http://blah.blogsome.com/2006/06/18/vi_tut_3/
[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040214/,如需轉載,請註明出處,否則將追究法律責任。

相關文章