Vi/Ex編輯器教程[4]

helloxchen發表於2010-10-22

第四章 替換命令

除了在這篇教程的上一篇中講過的全域性命令(:global)外在行模式命令中就數“:substitute”命令也就是替換命令最為精細和複雜了。在講完複雜的部分後我們就會開始接觸那些用以構建強力行模式命令串的技巧和竅門。




對當前行進行替換

你們中的大多數人應該已經知道替換命令的最簡略的寫法是“:s”並且已經以這種形式:

     s/previous/former/
     %s/Smith/Lee and Smith/

分別用來為當前所在行做替換和對檔案中的所有行作替換。你如果已經在使用這兩種形式的替換了,那你的學習進度已經超前了。有太多的課堂講師和教課書的編撰者都告訴你要在所有行中替換某個短語要使用類似下面的命令:

     global/Smith/s//Lee and Smith/

這只是在浪費時間。兩種的形式乾的是一樣的活,但第二種形式要用掉更多的輸入時間和電腦解釋命令的時間。在兩個版本的命令中不管檔案中是否每行都有要替換的“Smith”亦或整篇文件中只有一個“Smith”,替換命令都能很正常並且安靜地完成工作。

但兩種形式的命令都不保證對檔案中所有的“Smith”進行替換。替換命令在預設情況下只對行中出現的第一個目標字串進行替換,因此像下面的這一行:

     inure to Smith's benefit only if Smith shall

在執行完兩個版本中任一版本的替換命令後成了:

     inure to Lee and Smith's benefit only if Smith shall

這個問題有一個行模式內建的辦法:要對一行中所有的目標字串進行替換可以在替換命令後加上小寫字母“g”,就加在最後一個“/”後。因此使用如下命令:

     % substitute /Smith/Lee and Smith/g

原來的行會被替換成:

     inure to Lee and Smith's benefit only if Lee and Smith shall

上面的命令稍加變化就更應用在其他場合上。假如正在處理一些表格,並想每行中的最後一個“k37”改為“q53”。可以用這條命令:

     % substitute /(..*)k37/1q53

這樣就行了嗎?如果你覺得訝異的話,那就請記住:在搜尋中使用萬用字元時,編輯器總是讓萬用字元所匹配的字串儘可能長。這個例子中被匹配的字串從行開頭的第一個字元開始一直到最後一個“k37”。

現在你應該試著舉一反三。使用什麼命令才能僅替換每行中的倒數第二個“k37”呢?這有點小難,所以我提供了一個參考答案可以和你自己的答案做個比較(見附錄)。




更多的字元

替換命令用搜尋式樣(即替換命令的left-hand-side pattern,簡記為lhs)來指示要被替換的文字。而,也許你已經知道了,你並不是每次都要輸入同一個搜尋式樣。如果你想要重用上一次使用的搜尋式樣可以使用空的搜尋式樣來表示上一個搜尋式樣――不一定要在替換命令中,在其他情況也一樣。因此下面的兩條命令是一樣的。

     /Murphy/ substitute /Murphy/Thatcher/
     /Murphy/ substitute //Thatcher/

兩條命令都會到包含“Murphy”的行,並將第一個“Murphy”改為“Thatcher”。

用來表示地址的搜尋式樣中使用的字元,同樣可以在替換命令中的搜尋式樣中使用。此外還有兩個字元是替換命令獨有的:字元“(”和“)”。 這兩個字元本身並不匹配任何字元,因此:

     substitute /^The cat and small dog show/
     substitute /^The (cat) and (small dog) show/

從給出的部分來看他們是一樣的命令。但替換命令除了查詢匹配式樣的字串外還能記住字元對“(”和“)”中的搜尋式樣所匹配的字串,以便在替換文字中使用。當替換式樣中包含“1”時,編輯器會將該字元(1)替換成在字元“(”和字元“)”之間的式樣所匹配的字元或字串。而替換式樣中的“2”則會被第二個“(”和“)”字元對中間的式樣所匹配的字元或字串所替代。依此類推――在一個替換命令中最多可使用9對這樣的字元。在被替換文字中的字元對甚至能巢狀:左起出現的第一個括號對用“2”,依此類推。所以如果將上面的命令補充完整:

     substitute /^The (cat) and (small dog) show/My 2-1 fair

在被替換後的行的開頭部分就像這樣:

     My small dog-cat fair

又或者你輸入了:

     substitute :up (and )(over )(the sky):212123

會對下面的第一行替換,替換結果如第二行所示:

     up and over the sky
     over and over and over the sky

(我在上面的例子中用了冒號“:”來做為分隔符來分隔替換命令的不同部分。之所以沒有用“/”則完全是為了方便讀者閱讀――因為讀者很容易將“/”、“”或“l”和“1”弄混。)

如前一個例子所示,對於簡單的文字搜尋式樣而言“(”和“)”的用處不大。它們真正的價值只有在搜尋一些容易輸錯的文字時才體現出來,可以避免因手工輸入而在被替換式樣中輸錯的可能。

有時你會用一些文字或式樣來幫助搜尋定位,這部分的內容將在替換時被放回原來的位置。這時這兩個元字元就能派上用場了。(為了準確地圈定被替換文字我們通常需要在式樣中包含該段文字週圍的文字以幫助定位,這些幫助定位的文字在替換後會被放到原來的位置上。)這裡有三個這種替換方式的例子:

     % substitute :([Ss]ection) ([0-9][0-9]*):1 No. 2:g
     /([Ss]ection) ([0-9][0-9]*)/ substitute ::1 No. 2
     % substitute ,[Aa]nswer: ([TtFf] ),ANSWER: 1,g

第一個例子中,僅僅是將“No.”插入到文件中的“Section”與號碼之間。“(”和“)”的作用就只是將原來的section和其後的號碼保留起來,這樣在替換後它的第一個字母S的大小寫就不會改變。

第二條命令的作用與第一條命令差不多,但第二條命令只對跟當前行最近的一個匹配項進行替換(不對當前行的匹配項進行替換)。這裡的特殊之處在於我在地址式樣中使用了“(”和“)”。當然,行地址本身並不使用這兩個字元,但這也不會影響它找到正確地地址。行地址在尋找自己所匹配的行時會將這兩個字元忽略,但在接下來的替換命令重用了上一個搜尋式樣時,這兩個字元也會做為搜尋式樣的一部分一起傳給替換命令。第二條命令就是這樣情形。

第三例子對整理習題答案很有用。它查詢每一個“Ture”或“False”判斷題的答案,並將詞“answer”轉成大寫。這條命令的創新之處在於:它在單詞“answer”後面查詢“T”、“t”、“F”和“f”(即上面提到的幫助定位的文字),這樣當後面的答案是數字而非“true”或“false”時,“answer”的大小寫就不會發生變化。另外用來表示“true”與“false”的字母(“t”“f”),在替換後並不會發生改變。但是這個例子的教育意義大於實際意義――因為稍後我們會看到有些字元可以輕易地完成這個例子所完成的任務。




在替換式樣中使用的字元

當你想透過替換命令來新增一些字時,你可以使用一些僅在替換式樣(替換式樣即替換命令的right hand side,簡記為rhs)中使用的字元。它們與搜尋式樣所使用的字元完全不同。

&
在替換式樣中,“&”表示替換命令中的被替換部分的文字。當你純粹想加入而不是替換一些文字時就使用這個字元。例如:要將“kit”改為“kit and kaboodle”(忽略kit首字母的大小寫)時,可使用如下命令:

          % substitute /[Kk]it/& and kaboodle/g
     

如果你關掉了“magic”選項,就必須在“&”前加上反斜槓以使用它的元值。而在開啟magic選項後,在“&”前面加上反斜槓可以讓它成為一個普通的字元。

~
“~”用來表示在上一個替換命令中的替換式樣。使用這個字元的一個範例是用來改正某一個字詞的各種拼寫錯誤:

          % substitute /[Ff]anstock clip/Fahnestock clip/g
          % substitute /[Ff]ahnstock clip/~/g
          % substitute /[Ff]ahnstocke clip/~/g
          % substitute /[Ff]annstock clip/~/g
          % substitute /[Ff]anestock clip/~/g
          % substitute /[Ff]aenstock clip/~/g
     

如果你關掉了“magic”選項,就必須在“~”前加上反斜槓以使用它的元值。而在開啟magic選項後,在“~”前面加上反斜槓可以讓它成為一個普通的字元。

U
“U”字元使其後一直到替換式樣結束的所有字母轉成大寫,除非中間遇到了可將大寫功能關閉的其他字元。下面是將某段文字全部轉成大寫的命令:

          1 , substitute /.*/U&
     

L
“L”字元與“U”的作用正好相反:其後的所有字母將被轉換成為小寫。可用如下命令將文字替換為小寫形式:

% substitute /FORTRAN and COBOL/L&/g

E
“E”字元用來限制“U”或“L”的作用範圍。在“E”之後的字母將保持原來的大小寫形式。下面的命令將當前行用大括弧括起來,並將第一個單詞轉為大寫形式:

          substitute :([^ ]*)(.*):{U1E2}
     

如果你想要將“U”換為“L”你不需要在它們之間加一個“E”,反之亦然。當“U”或“L”的某一個出現時,另一個字元的作用停止了。假設你有一份書名的清單――每行一個書名並且只有每個單詞的首字母大寫,你現在想將書名中出現在冒號(“:”)之前一部分大寫,而其餘部分小寫,只要輸入:

          % substitute ,(.*):(.*),U1:L2
     

u
這個字元將緊隨其後的那一個字母轉為大寫形式。如果在該字元后的不是一個字母,則“u”沒有作用。

l
與“u”一樣,不同之處在於“l”將緊隨其後的那個字母變為小寫。

在替換命令中重用式樣還有一點需要注意。當含被替換文字的式樣(lhs)被部分或全部用於替換命令中的替換式樣(rhs)時,該重用命令引用的是前者所匹配的結果,即替換命令引用前面的式樣時,並不引用式樣中的字元,而是直接引用前面式樣中字元匹配的結果。反之亦然。這樣做的原因是,替換命令兩邊所使用的是不同的字元,字元在兩種型別的式樣中的含義不同,字元在原來的環境中有意義在新的環境中則未必,因而,只能是引用結果而不是字元。

不過當一個被替換式樣被重用於另一個被替換式樣中時,或者當一個替換式樣被重新用於另一個替換式樣中時,重用命令只是簡單地將原來的全部字元帶到新在式樣中,這些字元將在新的位置中重新對文字進行匹配。因此在這種情況下,重用命令的匹配的結果,並不一定與原來匹配的文字一樣,因為它引用的只是字元而不是結果(譯註:同樣字元在新的位置可能會匹配新的值,如'/^./'匹配的是每行的第一個字,但不同行的第一個字可能是不一樣的。而上一段所述情形中因為引用的是結果,假設'/^./'在上一次使用時匹配的是“在”,那重用時引用的是就是“在”。)。

現在我們再來做一個練習。有一個檔案,我們想將其第237至289行中的每個單詞的首字母大寫,其餘的字母小寫。但我們事先不知檔案中的大小寫情況,有些行可能整行都是大寫,或者都是小寫,甚至是大小寫交雜。我們假設詞與詞之間用空格隔開。有沒有辦法簡單地用一條行模式替換命令來達到我們的目的呢?做這個練習需要的一些東西,我在文中並未直接提及。所以如果我的答案比你的要簡單得多也不要覺得沮喪。




替換命令的其他用法

雖然“:substitute”是名稱是替換命令,但它並非一定要從行裡取出取出一些東西,然後再放一別些東西到原來的位置上。下面這個例子中,替換命令將一些文字放到指定範圍內的所有行開頭――它只是加入了一些內容,並未替換掉什麼。

     537 , 542 substitute /^/WARNING:  /

下面在命令執行前的文字:

     The primary output line carries very high voltage,
     which does not immediately dissipate when power to
     the system is turned off.  Therefore, after turning
     off the system and disconnecting the power cord,
     discharge the primary output line to ground before
     servicing the output section.

現在變成了這樣:

     WARNING:  The primary output line carries very high voltage,
     WARNING:  which does not immediately dissipate when power to
     WARNING:  the system is turned off.  Therefore, after turning
     WARNING:  off the system and disconnecting the power cord,
     WARNING:  discharge the primary output line to ground before
     WARNING:  servicing the output section.

使用替換命令來移除一部分的文字也是比較實用的技巧。下面的兩個命令就是這方面的例子:

     % substitute / uh,//g
     . , $ substitute / *$

後一個命令將行末多餘的空格刪除。最後的兩個斜槓可以省略,原因我們並不在原來空格的位置上置入新的內容。

上面提到的兩種用法你可能會經常用到,那你一定沒這樣用過替換命令――既不增加文字也不刪除文字。聽起來這樣做沒多大意義對吧?這裡就有一個這種用法的例子,在我寫這一系列的教程的過程中我有時會使用這個命令。

     % substitute /^$

這裡我幫你準備了一個不一樣的練習:我已經給過你一條命令了――就是上面那一條命令。很明顯的那條命令並未對檔案進行任何的修改,那我為什麼要使用這樣的一條命令呢?要回答這個問題你需要有一些想像力,所以如果你需要“參考”答案的話也不要覺得不好意思。




指令碼入門

現在你對這個編輯器已經有了不少的認識了,是時候嘗試一些複雜點的編輯任務了。下面是一個簡短的介紹,透過介紹你可以一窺寫編輯指令碼的這門藝術。

自下而上的程式設計方法。通常這是構建複雜編輯命令或命令指令碼的最好方法。這裡使用的是一個程式設計的術語,意思是先獨立地處理每個細節的問題再將它們放到一個統一的架構中。而不是由一個整體的架構開始,再去強迫細節來適應這樣一個架構。

例如:來自加洲舊金山的讀者R.T.問道“如何能讓vi編輯器自動地在每一段文字前後加入HTML的段落標籤?”也就是說要在每一段第一行之前加上段落的開始標籤“

”,而最後一行後加上“

”標籤。在這些文字中,段落與段落之間將由一完全空白的行(行裡不能有空格,甚至不能有不可列印字元)分隔。

這看起來相當容易。我們只需要找到先空行,先後在每個空行處向上移動一次來插入結束標籤,再移到空行下以插入開始標籤。但下面這個相當直白的命令還是有些瑕疵:

     global /^$/ - substitute :$:: | ++ substitute /^/

/

第一個問題是:許多檔案開頭處會留一空行,而當編輯器找到這一行時它會試圖上移一行,但因為沒有上一行所以它無法完成第一個替換操作並且還停留在原來的位置上。這時當它下移兩行時,它變成在段落開頭的第二行而不是第一行――很顯然我們並不希望將開結標籤放在這裡。不過我們有一些辦法來解決這個問題:

讓編輯器標識(:mark)空行,再去進行第一個替換,然後回到已標識的行,下移一行並進行第二個替換操作。

將第二個替換操作的地址由“++”改為“/./”,使之移至下一非空白行再進行操作。這樣不管當前行是在一空行上或在在空行上面一行,“/./”都能將替換命令帶到下一個段落的第一行。

將上面的命令分成兩條。仍使用:global來查詢空行,但每條命令只執行一種操作(加入開始標籤或結束標籤)。

第二個問題是:在兩個段落之間也許有不只一行空行,這些空行並不會影響HTML頁面的顯示。如果編輯器依前面的命令中找到了空行,而這一空行是在連續兩行甚至多行空行中的第一行時,我們在開頭給出的命令中的第二個替換命令會應用在第二行空行上。然後:global回到第一行空行,然後移到下一“空行”――而這一行正是剛進行完替換的那一行(當然,此時這一行因為剛增加了一個開始標籤已經不是空行了。但是記得嗎,在執行後面的替換命令之前:global已經先對所有的行進行標識了),並對其執行第一個替換命令。也就是說一段像下面這樣的文字:

     at this meeting, so be sure to be there!
	
     At next month's meeting we'll hear from the new

在修改完後應該像這樣子:

     at this meeting, so be sure to be there!
	
     

At next month's meeting we'll hear from the new

但實際上修改後的結果卻是:

     at this meeting, so be sure to be there!
     
     

At next month's meeting we'll hear from the new

當然這個“災難”似乎可以透過將上面針對第一個問題的第二個解決辦法做點修改來避免。也就是前兩個替換命令前面的地址改為搜尋式樣形式的地址,分別向上和向下找尋第一行非空行再執行替換操作。當對連續的空行中的第一行命令時,這的確能行。但從第二行開始,替換命令會對已經新增標籤的行重複新增標籤。於是示例文字現在看起來像這樣子:

     at this meeting, so be sure to be there!
	
     

At next month's meeting we'll hear from the new

多重執行條件。其實,這裡需要的是雙重的執行條件。即替換命令執行前要先同時滿足兩個條件:

  • 被替換行緊鄰當前所在的空行
  • 被替換行本身不是空行

編輯器能應付這種情況。當限定替換命令中的地址只能上移或下移一行時,命令中的 :global 部分就能滿足第一個條件的要求。(在前述第一個問題的第一個和第三個解決辦法中都能很好地滿足第一個條件。)要滿足第二個條件可以讓替換命令從現有行中移除一個字元,然後再將它放回去(即替換文字與被替換文字是一樣的)。這可以確定一個行是否空行,空行的話則替換操作失敗。

第一和第三個解決辦法經簡單修改都能滿足第二個條件。下面的示例命令中我用了第三種解法,因為它所使用的技巧比第一個更好理解一點:

     global /^$/ + substitute /^./

&/ global /^$/ - substitute :.$:&

:

再給個更進一步的例子,就能對自下而上的技巧有更深入的瞭解。讀者R.T.可能將大標題和副標題放在一起了,並且可能已經在大標和副標的前後都加上相應的標籤了。作為練習你可以思考一下如何修改之前的命令,使之在加入段落標籤時能跳過前面或後面已經有HTML標籤的行?提示――一個HTML標籤總是以“”結束。只需要對之前的命令作少量的修改就能完成這個練習,所以你可能不需要看解答除非你想再確認一下答案。

小技巧。要充分地發揮命令的威力我們需要靈活地使用替換命令。有時是“非常規”地使用替換命令,但――有什麼關係呢?管用就行。這種使用方式可能是當初這個編輯器的作者所未料想到的。下面是其中一些可能會對你有用的技巧。

你沒法對跨行的內容進行替換――至少不能直接替換。如果你以為在替換式樣和被替換式樣兩邊多放一個換行符的方法可行的話,那你就錯了。但如果你將全域性命令與替換命令結合使用的話,你通常能夠得到與跨行替換相似的結果。

假設這樣一個情形:你需要對一份很長的文件進行修改。所有的“Acme Distributors”都要改成“Barrett and Sons”。一個簡單的替換命令能夠完成大部分的修改工作,只是會漏掉“Acme”出現在行末而“Distributors”出現在下一行開頭的情形。再補充兩條替換命令分別對行頭的“Distributors”和行末的“Acme”進行替換的話可能會帶來破壞性的結果――這篇文件中也有“Acme Supply Co.”的紀錄,並且還有其他三間公司的名字是以“Distributors”結尾的。

但下面的兩條命令能很好地解決我們所遇到的困難:

     global /Acme$/ + substitute /^Distributors/and Sons
     global /^and Sons/ - substitute /Acme$/Barrett

第一條命令找到所有以“Acme”結尾的行,然後下移一行,只有下一行以“Distributors”開頭時才將“Distributors”替換為“and Sons”。第二條命令是第一條命令的逆操作,確保只將符合條件的“Acme”替換為“Barrett”。(注意:第二條命令中全域性命令查詢的是“and Sons”而不是“Distributors”,因為在第一命令執行過後被換行符分隔的“Acme Distributors”已經被替換為“Acme and Sons”了。)

分步驟地對內容進行修改是一個好的編輯策略。透過對需要修改的部分逐步進行修改來獲得想要的結果。想象一下,你現在是一位技術專欄的寫手你剛為相當數量的相片寫完標題――都是些類似“右邊上方光源”或“左邊邊緣暗色調”的標題。隨後你就得到了藝術總監將在排版時以水平翻轉的方式使用相片的訊息。突然間在“右邊”的變成了在“左邊”,而在左邊的變到了右邊。

現在你需要將標題中的“左邊”和“右邊”,互換。但除了在文件中逐個搜尋替換外,還有更好的辦法嗎?下面的看似直截了當的兩條替換命令可不怎麼管用:

     % substitute /左邊/右邊/g
     % substitute /右邊/左邊/g

第二條命令並不能達到將原來的“右邊”改為“左邊”的目的。在進行完第一條命令後標題中只剩下“右邊”,而第二條命令又將所有的“右邊”換成了“左邊”。我們需要使用一條過渡用的替換命令來達到我們的目的:

     % substitute /左邊/QQQQ/g
     % substitute /右邊/左邊/g
     % substitute /QQQQ/右邊/g

第一條命令將“左邊”暫時地更換為“QQQQ”(當然也可以是任何未在文件中使用的字串),這樣你就可以放心地使用第二條命令了。然後在執行第二條命令後,第三條命令再將“QQQQ”換成你將要的字串。

有時故意輸入錯誤的文字,再使用替換命令將它們改回來並不全然是找罪受的舉動。當我在寫純文字格式的文件時,我經常使用一些文字“線”段來分隔主要的段落。有些人簡單地用一串的短槓(或其他單一字元)來做分隔線,但我喜歡使用有多字元組成簡單圖案的分隔符。下面是這種分隔符的幾個例子:

     -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
	
     -+--+--+--+--+--+--+--+--+--+-
	
     *~*~*~*~*~*~*~*~*~*~*~*~*~*~*~
	
     [][][][][][][][][][][][][][][]

其實我沒那麼大的耐心去輸入一整行的不同字元――尤其是當我必須不停地按Shift鍵時。我只要按住任意一個鍵讓它佔滿一行――不管這是哪個字元再後都將被改為我想要的圖案。對於上面的四個圖案,我只要先分別輸入下面四行:

     ------------------------------
	
     ------------------------------
	
     ******************************
	
     [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[

然後我只要執行幾條相似的替換命令就能得到我想要的分隔符了。這裡是我對上面四行分別使用的替換命令:

     substitute /--/-=/g
     substitute /---/-+-/g
     substitute /**/*~/g
     substitute /[[/[]/g

半自動替換。在文字編輯中有一些替換操作相當依賴於人的判斷和觀察,無法完全使用自動的替換操作來達到我們的修改目的。但在有些情形下我們仍可以透過以下的兩種手段讓編輯器完成部分的修改工作。

第一種是使用替換命令的修飾符使用之在進行替換時提示――這時使用者可以選擇進行或不進行替換。你只需要把“c”放在替換命令後面就可以以這種方式進行替換。如果你同時還使用了“g”就可以對行中所有所有符合被替換條件的字串進行操作,請參考下面的例子:

     % substitute /^someth
     ing/something else/c
     % substitute /something/something else/gc

vi編輯器會在螢幕上逐一顯示即將進行替換的行。在被替換文字的下面會有“^”的標誌,像這樣:

something in the air. The consensus is that
^^^^^^^^^

如果在即將進行替換的行中有兩處或兩處以上符合條件的被替換文字,該行會被多次顯示。並且每次“^”標誌將指向準備操作的被替換文字,等待並接受使用者輸入直到使用者輸入了回車。如果你的輸入以“y”開始不管你要輸入什麼,替換都會馬上進行。如果輸入的是其他的內容,則不對當前文字進行任何操作。

不過這種形式的替換操作對你來說可能還不夠靈活。你可能需要透過觀察更多行才能決定是否該進行替換;又或者在不同的位置你需要使用不同的文字來代替原來的文字。在這些情況下,我們可以藉助:global命令的一個特性來解決問題。這是我們的程式設計師同志小何用過的技巧的一個簡化版本(見本教程的第一篇)。

和以前一樣,如果你在可視模式下的話你要先輸入大寫的“Q”進入行模式。在行模式下的冒號提示符後,輸入下面的命令(假設你要進行與上一個例子一樣的替換):

     global /something/ visual

這條命令依序將你帶到包含字串“something”的行, 並自動轉入螢幕編輯模式。在你觀察並做完替換後(如果需要的話),只要輸入大寫的“Q”就可以離開螢幕編輯模式回到行模式,這時global命令會繼續執行並將你帶到下一符合條件的行並重新進入螢幕編輯模式。

不過,在離開螢幕編輯模式的時候要當心一些意外的狀況. 那可能使我們失去所有的修改,甚至是丟失整個檔案。這聽起來很糟,因而我們接下來將對如何保護勞動果實方面的議題進行探討。




看好你的檔案

vi/ex編輯器在預防使用者誤操作進而引起災難性後果的能力稍嫌不足。當然這也是當使用者被賦於充分編輯自由的必然後果。不過如果我們編輯了老半天,修改的成果卻沒被儲存下來;或者不小心丟失了原本的文件時,我們當然希望編輯器能夠在我們靈巧的手犯錯之前能給點提示。幸運的是使用者還是有很多方法可以用來保護自己免受災難“眷顧”的,我將在下面提供一些方法:

在緊急情況下。這個編輯器有一個基於保護目的的功能可能會偶然地造成災難性的後果。你可能已經知道當你用vi編輯時你只是在編輯文件的一個幅本,而不是原本。在你使用寫入命令(:write , 縮寫是:w)或以正常方式退出編輯器之前並不會影響原文件。這本是很好的保護機制用來防止這個威力過於強大的編輯器偶然地“損壞”你的檔案――更糟的是你可能事先沒有備份。

你正在編輯修改的檔案幅本處在一個相當不確定的環境中:作業系統可能崩潰或斷電,你會因此丟失檔案副本――而你做的修改都在檔案副本中。副本丟失後你之前所做的工作就打水漂了。阻止災難侵襲的第一道防線就是經常地使用:write命令――每使用一次寫入命令,你所做的最新的修改就會被儲存到穩定的磁碟空間中。

那如果不想動到原來的文件呢?如果想把編輯的結果儲存為新檔案,不更動原來的文件呢?這樣的話你需要對寫入命令進行一些“加工”。輸入:“:write nufile”。看你想要新文件起什麼名字,將“nufile”改為你想要的名字。(如果你不希望儲存到當前資料夾下的話,那你還要在檔名前加上路徑。)這條命令會將修改後的文件儲存到新的檔案中――原文件則儲存不變。

但上面那種保留原有檔案的方法還是有風險的。因為只要有一次你在使用:write命令時,在輸入檔名之前“不小心”按了回車,那你原來的檔案就會被覆蓋。所以上面提供的技巧還是在你既想改變原來的文件又想另存為新檔案的時候使用吧。當你進行完一階段的修改編輯工作後執行下面的兩條命令:

     write nufile
     write

然後再繼續對文件進行編輯。

避免誤修改原文件的更穩妥的辦法是用這條命令開始你的編輯工作:“:file nufile”――也可以寫成這樣:“:f nufile”。在執行該命令後編輯器就會把“nufile”當成是當前修改中的檔案的名稱,再使用寫入命令時就會自動寫入到“nufile”中。(如果你一時忘了是否已經改了一個新的名字或忘了新的名字是什麼時,直接輸入“:nufile”不用帶檔名,編輯器就會顯示當前的檔名――還有一些其他的資訊。)

天有不測風去。不管怎樣做,我們還是有時會不巧地??上一些突發事件。後果是我們沒還來得及儲存的勞動成果就附之流水了。為了預防這樣情況出現,編輯器會試圖在崩潰的過程中儲存你的文件。在一些情況下,使用者會有一些時間來執行儲存命令,比如你因為超出檔案空間配額無法寫入。這時可以輸入:preserve命令(或者它的縮寫形式:pre)這樣當前的文件就能得以保留了。但進行這一操作還是有一些地方需要留意的。

preserve命令將當前文件儲存到一特定的目錄,如果目錄不存在或不具可寫許可權就會操作失敗。(這個目錄的路徑因不同的編輯器版本而有所不同。在大多數現代的Unix系統中這一目錄通常是“/var/preserve”。)為了測試是否可以寫入指定目錄,可以在內容較少的文件中執行:preserve命令作為一種測試方法。如果結果是類似下面的訊息:

     Can't open /var/preserve
     Preserve failed!

那你就得和系統管理員溝通溝通了。(你可以附上不能開啟的目錄及其路徑以期儘快得到管理員的回應。)如果是類似下面的訊息:

     File preserved.

目前為止還算順利。下一個問題是使用:preserve命令時編輯器是否已經完整地將文件保留(:preserve)下來了,還是隻保留了一部分――有些版本的vi編輯器就會在這裡出狀況。為了檢查編輯器是否完整地保留了文件,將剛保留的檔案恢復看看。

搶救保留下來的檔案。在經歷崩潰等突發事件或者使用了:preserve過後要試著恢復文件可以使用下面的兩種方法。通常情況下你是在命令列下透過執行帶“-r”引數的“vi”來恢復:

     vi -r novel.sect3
     vi -r

第一條命令開啟你那經過“搶救”的檔案“novel.sect3”,並將之置於編輯環境中。第二條命令並不打球編輯介面,而是顯示所有經“緊急儲存”的檔案列表(即可恢復的檔案列表),然後返回命令列。如果系統崩潰時你正在編輯的檔案尚未取名,這個列表就很有幫助了。(沒錯,你的確可以在賦予一檔案檔名之前就進行編輯。此時編輯器會先開啟一新的工作區並等你稍後為之命名。)在這種情況下,編輯器在儲存你的檔案的過程中會為它命令,而你必須知道到底哪一個才是它的名字以進行恢復。

就如前面說的第一條命令根據你給的檔名,開啟該檔案的最近一份經搶救成功副本。如果系統崩潰已經不是一次兩次了,那因你或由於系統原因讓編輯器自動儲存當前檔案也可能不是一次兩次了。如果最新的副本並不是最好的版本,那你可以選擇不從最新的儲存記錄中恢復。而是改用編輯器儲存的次新的副本。這個操作可以直接在編輯器中進行,只要輸入“:recover”命令(或用簡寫的形式“:rec”)。這樣最近的一個副本將被換成次新的版本。(因為你也經在編輯器中了,所以輸入命令時不需要再給出檔名。編輯器在預設的情況下以當前檔名為引數,否則編輯器會試圖恢復你給的檔名的經儲存的副本。)如果這還不是你要的那個版本,那你可以繼續使用“:recover”命令。

不管用什麼方式恢復,在恢復完後先瀏覽一遍。如果你所用的vi版本的preservation功能有缺陷,那你可能只得到一些散亂的字元,或是一些像這樣子的行:

     LOST
     LOST
     LOST
     LOST
     LOST

如果是這樣的話,那你之前用:preserve命令搶救下來的檔案已經丟失並且這很可能是無法挽回的結果了――你只能找系統管理員建議他將這個編輯器升級到更好的版本。話說回來如果你看到的正是你要的內容的話,那要趕緊將它儲存起來――自動保留的副本是你使用了恢復命令(:rec)後就會被自動刪除,所以別指望下次還能用恢復命令找到同一份檔案副本。

還有一個需要當心的地方。你可能相信當你使用“ZZ”“:x”“:wq”這三條命令的任何一條退出編輯器時,編輯器會先檢查當前編輯的文件是否被修改過,如果已經修改則先儲存修改再退出。實際上這三條命令中只有最後一條:wq總是將當前文件寫入檔案(不管當前文件有沒有被改動。),所以你應該只使用這一條命令儲存並退出vi以策萬全。

前兩條命令會做一些檢查,但只是簡單的檢查。具體來說前兩條命令和“:quit”命令僅僅透過內部的一個修改標誌來判斷當前文件是否經過修改。這個標誌在當前文件被修改時被設定,而當修改被寫入檔案後這個標誌被清空。當你在使用了“ZZ”或“:x”命令或者是不小心用了“:quit”命令時你可能需要吞嚥自已帶來的苦果了。

之所以這樣說是因為對於編輯器來說你開啟一個新檔案或是恢復一份經緊急儲存的文件到工作區(buffer)都是一樣的――都是視作未修改的檔案。如果你使用的vi版本中ZZ與:x命令的檢查機制比較鬆散那它會認為檔案開啟後並沒有進行過修改而不予儲存。這樣一來編輯器會自作聰明的選擇直接退出而你恢復檔案的努力在臨近完工之際功敗垂成。所以所以記得一定要使用“:wq”來退出vi。當然你也可以選擇分兩步完成,先用“:write”寫入修改,再用“:quit”退出,結束編輯。

其他狀況和應對方法。惡運還是可能隨時降臨。你可能會偶然地丟失自已所做的修改同時還破壞了當前編輯檔案的原本。

想象一下如果你可能因誤用全域性命令而將當前工作區的檔案弄亂,但碰巧全域性命令修改的部分沒在螢幕所顯示的區域中。這時你如果使用了寫入命令……慘!除了一些較小的檔案以外,我們根本不可能在每次使用:write命令前先仔細檢查一下當前編輯檔案的內容。

也許你的確在寫入之前及時發現了這個問題。並且意識到要撤消操作不太實際(因為你不知道是什麼時候哪個操作造成的),這時你可以用“:edit”命令來放棄上一次寫入操作(或開啟檔案)後的所有修改。也可以使用“:quit!”來放棄所有修改並退出編輯器。這兩條命令都命令末尾使用了“!”以表示忽略還沒寫入的所有更改。

不過因為你並不是在寫編輯器指令碼所以你可能輸入上面兩條命令的簡寫形式:e!和:q!來代替完整的命令。但是你要小心別打字打錯了――在標準的鍵盤上,“w”鍵被放在了“e”和“q”之間因而存在著打錯字的“危險”。一旦你不小心打字打成了:w!,那就自求多福了――這條命令讓編輯器強制將有問題的檔案版本寫入檔案正本――不管有沒有防寫。

如果你一直都在螢幕模式下進行編輯的話,那你還有最後一根救命??草。不管何時你都可以輸入短短的命令系列來將檔案退回到檔案剛開啟的狀態(不管中間有沒有使用寫入命令儲存修改,都能恢復到剛開啟檔案進行這一次編輯時的狀態)。這時只要再用寫入命令就可以將當前工作區的檔案儲存起來替換剛儲存的有問題的檔案。

輸入“Q”離開螢幕模式回到行模式下。在行模式中有一條“undo”命令作用與螢幕模式下的“u”命令相仿。這條命令撤消在上一個在行模式中對當前文件所做的更改。因此,行模式下使用“visual”後對文件所做的“所有”修改也被視為是行模式下的“一次”修改。然後,當你在命令提示符中使用“vi novel.sect3”命令執行vi並開啟相應檔案後,命令直譯器(shell)實際上執行的是ex(vi只是ex的一個別名),而編輯器一執行就給自己下了一條“visual”命令使文件在開啟後自動處於螢幕模式下。

所以從頭至尾,編輯器都有檔案原本的一份完整的複製。這是它的職責,因為使用者可能隨時回到行模式下撤消初始的“visual”命令。(這也是編輯器使用的暫存空間比一般的交換空間要多的原因。)如果你想看看撤回到檔案的初始狀態並重新回到可視模式下所用的命令系列的話,這裡是使用簡寫形式的版本:

     Qu
     w
     vi

最後一點提醒。對一些有經驗的Unix使用者而言下面的這樣情況出現有些搞笑,但許多剛從單使用者系統遷移過來的使用者的確會碰到這種情況:除非你在少數有檔案鎖定功能的Unix版本中工作,不然你的的確確沒辦法預防其他系統使用者在同一時間與你開啟同一份檔案進行編輯。

你們將在各自的工作區中進行編輯,你們完全沒法獲悉些時有人在編輯同一份檔案。每次當你將修改寫入檔案時,另一位使用者先前寫入的修改將會丟失,反之亦然。在這場互不知情的較量中,獲得最終“勝利”的將是進行最後一次寫入操作的人。而另一位使用者在一個多小時後再開啟檔案時會發現裡面完全沒有自己留下的編輯痕跡。

沒有什麼技術上的措施可以真正預防這種情況發生。你只能透過與其他可能需要對該檔案進行編輯的使用者溝通的辦法來解決這一個問題。




讀者來信

我們的一位讀者對這一技巧提出了自己的問題。由於這個問題的重要性,我覺得有必要將回復放到這篇文章中。

Walter,您好…

你在教程中提到可以用下面這條命令

          global/XXX/visual
     

搜尋式樣“XXX”,然後對其進行其他編輯操作(記得嗎,小何用這條命令來編輯他的意式麵條程式碼……)這就產生了一個疑問:如果文章中有100個“XXX”,而我只需要對前10個進行編輯,因此不需要再找其餘的90個“XXX”了。上面的命令只要我一輸入“Q”就會繼續找到下一個“XXX”。但我處理完前10個“XXX”處的程式碼了,我現在想要檢視/編輯程式碼中有“illegal”字樣的地方。所以我輸入“Q”然後使用命令global/illegal/visual。

問題就出在這裡:輸入Q並不會出現提示符等待使用者輸入命令而是直接找到第11個“XXX”出現的行。我想知道的是有沒有方法可以在我輸入Q時vi不再繼續執行global命令呢?

致禮!

Chris…

如同Chris所意識到的,如果我們不想再搜尋後面的90個“XXX”我們可以簡單地忽略它們(只要我們不再進入行模式)。每次命令執行時會將使用者帶到可視模式下,但使用者沒有被限制一次只能對一個地方進行修改。你可以像平時一樣隨意地上下頁翻動,對需要的地方進行修改――只要你願意你可以一直待在可視模式下。而當你在可視模式下進行完所有編輯工作後,你完全可以像平時一樣儲存檔案然後再退出編輯器。而那條在後臺靜靜地等待你再回到行模式下(以執行下一步操作)的global命令,在你退出編輯器後也跟著結束了。不過如果我們想用同樣的方法找到第二個字串時――正如Chris想做的那樣,我們就需要用些迂迴的方法。

走出困境的最好方式是先儲存修改(使用寫入命令)。然後,輸入:edit命令――先別急著用Q。這個命令會重新從磁碟上重新載入當前檔案到工作區中。因為你才剛進行寫入操作所以重新載入的版本與你原來在修改的版本的內容是其實是一樣的。同時因為你還沒有離開編輯器,各種設定如有名工作區,鍵盤對映及縮略設定還有一些設定項還保留著。只有少數專案發生了變化如未命名工作區(快取區)被清空了――而global命令也因此中斷執行。現在你可以用Q鍵進入行模式下並執行第二條global命令了。




下一篇

這系列教程進行到現在,你肯定會對這個編輯器的某些方面不太適應。好訊息是這其中有些東西是可以依使用者意願更改的――並不需要用修改原始碼之類的方式來更改。在這系列教程的第5篇中,我會詳細說明用以建立屬於自已的編輯環境的一些vi/ex的內建功能及許多可以透過這些功能進行修改的元素。



Appendix A 答案

begin 644 viex4_ans
M5FDO17BQX+RMQO+,]Z^S=#0P"K(%TJ.EQ,7'4F.FVSJ?3T,_>HZS+_,/'UKNVU,;DNO.UQ-*[N/;7UL2XT]#3L,_LH:/+
M^=+4LKN[X=;5UKFAL%Q,H;&L*&P7%6AL;7$U_?3PZ&C"@K7]]7?S.'*OJ.Z
MU-K0M,WJU>*X]K3PL+BY_;KSHZS.TK+%MZ+/UM3:T.VVX+7$=FFPYK&^UM#5
MXM;6M[VWJ-#0LKO-J*&CTO*TR[2T];4]KS3P+6UKWBMZBAHOD
MR+N[X<_ m="">EUBU=*EPI
M.EQU7#%<3%PR.FPO?3!
MVM?%L>JUX[?[NL7*L:.LM=K2N];6L.RWJ,.[MZB]J[7:TKNX]M?6Q+C7JLZJ
MM//0M*.LMOBUVK;^UM;$W*&CQ.._R=+4U]2NK[VMJC*Q[?QO:O3R*W
MJ-;0SN6X]L&LU]:W^[7$U^ZZ]*[N/;(I;7TOLW0T,'+H:,*"@H*"@JAL+*[
MMJ_7]Z&QM<3,YKN[P_S![@H*UKO+TTO_:CK+'@O*W&][[-N^'(SZJS.:[N[+9U_>SR;FF
MH:JAJKRTRKG+_-:[RL>]J[_5U]:TKJ.HLKO*Q[_5N/&CJISLK,X@H*SM+#Q]2MP+2UQ+>]MZC2JM3:
MTKNX]K;.PN36KL>PO-/)S['JQZFRR=/#M<2WO;>HRL>]J[7:TKNX]M?6Q+C2
MQK/]U-FPT=#"S.VT[7$L>K'JTIJ&CS];4VL[2P>[N+1^=?VL_W!R[6QTKO0T-+4H;`
MH;'*L:&CSM+#Q[_)TM30WKC$P_S![LJYM<.ul mrk="">PW*.LU>+1^*UX]:[TJJ]JSFN[O*O='YUM"UQ+7CHZ@NHZG7UM2JN,3.
MJM*[N/;7UK?[H;`H;&UQ+*YO*^CJ*&P6UX7:&QHZG0SLJ]HZS5XM'YOLW$
MW,:EQ>3(SKK.M>_U=#0M<2_JLVWHZRS_;?'N,/0T-+42%1-3+'JQZF_JLVW
MH:,*"LVLP.VCK+_)TM2]JSFN[O*O='YUM"Q[/7
MUM2JN,3.JK'MRKY(5$U,O>'*^+7$H;`^H;'7UK?[M<2RN;ROT,[*O:&CS+#
MYLK'T-ZXQ+KSM<3#_,'NH[H*"F=L;V)A;"`O7B0O("L@/%TO/%`^)B*9VQO8F%L("]>)"@+2!S=6)S=&ET=71E(#I;7CY=)#HF
'/"]0/CH*"@``
`
end
轉自http://blah.blogsome.com/2006/06/18/vi_tut_4/
[@more@]

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

相關文章