Vi/Ex編輯器教程[1]

helloxchen發表於2010-10-22

Vi/Ex編輯器教程

作者:Walter Alan Zintz
譯者:hq00e (at) 126.com
原文:

第一章 Vi基礎

為什麼選擇Vi

一個貼心的編輯器。小何是個程式設計師,在一公司裡與他人做共同維護的工作。剛從別人那裡接手了一個大的模組。這個模組的程式碼真是滿目瘡痍啊,到處是修補的痕跡。而且看上去這些“義大利麵條”式的程式碼補得不怎麼牢靠;就在昨天這個模組徹底地崩潰了,使得這個部門幾乎癱瘓。在一夜地奮戰過後,小何終於在今早使這個模組又能運轉了……在他打算出去買早餐時,該公司資訊部門的副主管走過來了……

“小何,這次的修復乾得很好,辛苦你了。不過現在我需要這次崩潰的技術資料,要整理過的,馬上。資訊委員會的董事會早上召開了一個緊急會議,目的是評估問題是否在可控制的範圍。如他們把矛頭指向我,那我就倒大黴了。我需要有一些可以在投影機上播放的技術資料,以便轉移他們的注意力。

“他們很可能會讓我講一講日誌中導致這次崩潰的錯誤程式碼的相關記錄……對了,這部分內容是記錄在 /oltp/err/m7 中,日誌是使用追加的方式因此最新的報告會記錄在檔案的底部。那些人對日誌中舊的部分不感興趣,他們認為那是歷史了。另外除了市郊的火車時刻表外,他們不習慣看東西是從下往上的。所以你得重新整理一下順序。

“看一下,這是日誌檔案:

     374a12  44872  130295/074457  nonabort
     5982d34  971  130295/221938  nonabort
     853f7  2184  140295/102309  abort
     ……

“恩……向他們解釋第二欄的資料等於跟他們說我們早知道這些缺陷的存在,只等著模組崩潰了──那是找死。你在編輯時記得,記得將第二欄中除首尾的兩個數字外的其他數字刪除。

“對了,他們看那些看膩了後會想仔細地看一下Lint報告的。上個月我才跟他們說我們的程式碼無懈可擊,現在我得說服他們相信這個模組現在還在不斷輸出的錯誤訊息都是些無關緊要的小毛病引起的。你得對修補後的程式碼進行Lint檢測然後把輸出結果與原始檔合併。方法是先在輸出的結果中找這種的資訊:

     Line 257: obsolete operator +=

然後把重要的部分放在原始檔中相應行的末尾。中間用分隔符――如XXX分開,方便查詢。沒什麼能比足量的原始碼更能讓會議提早結束了因為他們根本不知從何看起。

“快去做吧。會議在35分鐘後就開始了。”

然後我們的副主管就走了。他是暗笑著走開的,因為他已經打好算盤了――他知道在這麼短的時間裡沒人能做好他要求的那麼多的編輯任務。這樣等會他就不用費力的解釋這次的崩潰了,他只需把責任推給他的下屬。我就跟資訊委員會的人說:“我已經跟程式設計師說過要在9:30之前做好報告了,而且講很清楚了。但我剛問他時他說還沒弄好而且不知何時會弄好。”然後:“這些程式設計師就是不能意識到時刻向管理層報告進度就跟程式中的每一個位元組一樣重要!”

不過小何在與上級的角力中並非完全落於下風,他還有秘密武器:vi

將檔案中的行倒置對這個編輯器而言只是小菜一碟。以下的八個按鍵(在以下的文章中用(ret)表示按Enter鍵):

     :g/^/m0
     (ret)

就能完成這個工作了。將檔案中所有行的第二欄首、尾以外的其他數字刪除也只要一行命令:

     :%s/^([^ ]*  [0-9])[0-9]*([0-9]  )/12
     (ret)

那結合Lint報告與原始碼呢?就算這種工作Vi也能自動做到自動化。這條命令:

     :%s/Line ([0-9][0-9]*): (.*)/1s;$; XXX 2
     (ret)

會把Lint的報告檔案改為編輯器的指令碼,只要在原始檔中執行此編輯器指令碼就能達到我們要的編輯目的了。

小何只用了幾分鐘,輸入了幾行就避免了當冤大頭。他現在還剩一些時間可以考慮怎樣才能防止副主管推諉責任――他可以先到街對面的咖啡廳,等在會議開始的那時再再出現在會議廳中,並用在場每個人都聽得到的方式告訴副主管:“你要的那些檔案就在‘斜槓 temp 斜槓 hal’資料夾中”。

這篇教程的寫作計劃。我想寫給那些對 vi/ex 有初步認識的編輯器使用者。即你已經對一些類似“Vi 入門”之類的書裡教的那些普通的內容已經熟悉了。這種Vi的書籍在市場上氾濫卻很少觸及更深層次的東西。

在這系列的教程中我們會深入的探索一些較不為人知的 vi/ex 的用途。其中有不多的技巧是透過一些我們經常使用的編輯功能來實現的,但我們確很少注意到這些技巧――舉例來說,用 global 命令來對處理的每一行做記號。同時我還會對關於Vi的許多常見的誤區進行闡述。

要做到這些,我會很詳細的解釋裡面的每個部分。我會在有必要的地方出些習題幫助理解。同時為了讓你不至於被過多的模糊的資訊所淹沒,我會將這篇教程分成很多小塊。然後用平穩合理的節奏將教程一篇篇地放到我們的網站上。

關於這個編輯器的幾個基本概念

要真正理解這個編輯器的威力,你得對編輯器有一個基本的認識。它的許多功能便是築在這些基礎的概念上面。

這個編輯器被誤用的一個原因是許多使用者包括有經驗的使用者,沒有分清什麼是它的本職而哪些工作不適宜用它來完成。這裡有一個這個編輯器本職功能的參考列表:

第一,編輯器嚴格地用來表示通用目的的編輯器。它不對文字進行格式處理;它不需要一個字處理器的支援;它不需要內建特殊的功能用來編輯十六進位制、圖形、表格、大綱或是支援任意一種程式語言──Lisp除外。

它是二合一的編輯器。在可視模式(Visual Mode)1下,它是個比大多數的編輯器好的全屏編輯器,並且比那些同樣支援一堆螢幕編輯命令的對手要執行得更快。它的行模式2(Line Mode)使字處理器和簡單的互動式編輯器的“全部查詢和替換”功能相形見絀。它的僅有的對手是非互動式的編輯器,如Sed ,使用這種編輯器你得事先準確的知道自己要做哪些編輯。但在 vi/ex 中,這兩種工作方式被很發好地結合起來了。我用過的編輯器還沒有哪一個會比 vi/ex 好――當把它的兩方面的優勢結合起來時。

最後,這個編輯器只有抱著不怕苦不怕難的精神完整地學習過才能夠用得得心應手。它的功能太多了,你根本沒法在一或兩個鐘頭裡掌握。它又很特殊,用一個禮拜的時間都沒法精通這個編輯器。但它的威力就在那兒,只有深入鑽研的人才能掌握。這種威力很大一部分要靠對編輯器的個性化程式設計來實現:這不太容易,對熟練的使用者而言它透過程式設計來擴充套件功能的能力要比任何的其他編輯器來得強或許(可能)只有Emacs例外。

搜尋式樣

在這個編輯器有有許多的功能,能透過使用字串式樣的搜尋來指定功能在哪裡執行,範圍有多大。這些搜尋式樣很能說明這個編輯器帶有明顯的Unix風格,但在搜尋式樣的細節上仍與其他的Unix工具有所區別。

搜尋式樣功能在行模式下與可視編輯模式下大多數情況下有一樣的使用方式,只有少數例外。但在輸入搜尋式樣時怎樣讓編輯器根據不同的需要進行搜尋呢?

從當前位置開始搜尋。通常我們用搜尋式樣是為了移動到檔案中的另一個地方,或者把編輯命令的範圍從當前位置擴充套件到式樣所指明的位置。(在編輯模式下你還可以用一個式樣的位置到另一個匹配式樣位置作為執行動作的範圍,但兩個式樣都是從當前位置開始搜尋。)

如果你想要從當前位置往下搜尋(一直到檔案尾),在搜尋式樣前面和後面加上一個斜槓(/)。因此如果你想要從當前位置開始查詢檔案中出現下一個字串“j++”的位置,輸入:

     /j++/
     (ret)

就行了。也可以這樣:

     /j++
     (ret)

注意,在式樣與Enter鍵(ret)之間沒間隔,回車本身表示搜尋式樣的結束,所以第二個斜槓可省略。如果當前模式是可視模式,使用 ESC 鍵與Enter鍵一樣可以用來表示搜尋式樣輸入完畢,所以在可視模式裡如下命令

     /j++
     (esc)

與之前的命令的作用是一樣的。

要向上搜尋(回搜),在式樣前後加上問號而不是斜槓。回搜與向下搜尋使用同樣的規則,因此

     ?j++?
     (ret)
	
     ?j++
     (ret)
	
     ?j++
     (esc)
	

都可用於回搜一樣的字串。

無論哪種方式你都只用了一個鍵來提交併確認搜尋的式樣和搜尋的方向。可別以為回搜時在檔案中找到的匹配項都是在當前位置以上,當前位置以下的匹配項也會被搜尋到,反之亦然。編輯器在往回搜尋時先在當前位置逐漸向上搜尋,但當它到達檔案的頂部(第一行)或底部(最後一行)時如果還沒發現匹配項,它就會從檔案的另一端開始往同一方向繼續搜尋。也就是當你用問號進行回搜時,編輯器會從當前行不斷地往上找。如果到第一行還沒找到匹配項,那它就會從最行一行開始繼續搜,然後是倒數第二行,倒數第三行……依此類推,一直搜到最初開始搜尋的位置(如果一直沒找到匹配項的話)並停止搜尋。或者當你往下搜尋時一直到最後一行也沒找到匹配項,編輯器就會從第一行開始繼續搜尋,然後第二行,第三行……

如果你不想在搜尋從檔案的一端繞到另一端去繼續,你需要一條行模式的命令:

     :set nowrapscan
     (ret)

這可以禁止當前會話3中的繞回搜尋。要在編輯時恢復繞回搜尋功能,輸入

     :set wrapscan
     (ret)

就可以了,看你想開開關關幾次都可以。

“查詢全部”搜尋。目前為止,我只討論了查詢搜尋式樣的一個匹配項的方法――往指定的搜尋方向查詢在檔案中距當前位置最近的一個匹配項。但搜尋還有一種其他形式。這種形式的搜尋主要用於行模式命令,如 global 和 substitute 。這種搜尋方式找到檔案中(或在檔案中的指定部分)包含式樣的所有行並對它們進行操作。

在使用 global 和 substitute 時不要被它們搞暈了。在命令列中兩種形式的搜尋方式經常混合使用。但“查詢一個匹配項”的式樣一般置於命令名或命令縮寫前而“查詢全部”的式樣則放在命令後。比如,在以下命令中:

     :?Chapter 10?,/The End/substitute/cat/dog/g
     (ret)

前兩個式樣分別用來匹配在當前行之前且距當前行最近的包含“Chapter 10”字串的行和在當前行之後的第一個包含“The End”字串的行。請注意每個地址(即包含式樣的行的地址)只對應一行。以半形逗號隔開表示 substitute 命令作用於這兩行和這兩行之間的所有行。但緊跟著 substitute 命令的式樣指示命令對命令作用範圍內的所有字串“cat”替換成“dog”。

除了字面上意義的不同外,兩種形式的搜尋也使用不同的分隔符用以標識式樣的開始和(有些時候需要)結束。對“查詢全部”式樣而言不需要指示往前或往後搜尋。因此式樣的分隔符不僅限於問號和斜槓,還可以用半形的標點符號――幾乎所有的標點都可以,因為編輯器自動識別命令名後的第一個標點符號為該命令中的分隔符。因此這幾個替換命令

     :?Chapter 10?,/The End/substitute;cat;dog;g
     (ret)
	
     :?Chapter 10?,/The End/substitute+cat+dog+g
     (ret)
	
     :?Chapter 10?,/The End/substitute{cat{dog{g
     (ret)
	

都是一樣的。(最好不要使用在命令中有特殊意義的標點,如感嘆號經常用作一個開關選項出現在命令名的後面。)

使用其他標點作分隔符在搜尋式樣本身包含“斜槓”時就顯示出了其優點。例如,當我們要將文字中所有的連續的兩個“斜槓”用“-”隔開,即將“//”替換為“/-/”。很明顯這樣做:

     :?Chapter 10?,/The End/substitute/////-//g
     (ret)

是不行的。這個命令會將前三個斜槓視為分隔符,而對後面的所有字元視而不見。這個問題可以用“反斜槓”來解決:

     :?Chapter 10?,/The End/substitute/////-//g
     (ret)

但這個比上一個錯誤的命令還難正確地輸入。當用其他的標點作為分隔符時

     :?Chapter 10?,/The End/substitute;//;/-/;g
     (ret)

容易輸入也更容易理解該命令的作用。

簡單搜尋式樣。最簡單的搜尋式樣是直接輸入要編輯器查詢的字元。就如:“the cat”。但這裡有幾條注意項:

  1. 這樣方式搜尋字元時並不只有以這些字元組成的單詞會匹配式樣。它會匹配“we fed the cat boiled chicken”也會匹配“we sailed a lithe catamaran down the coast”,就看它先碰到哪個。
  2. 至於會否匹配“The Cat”則要看你是否設定了一個叫 ignorecase 的編輯器變數。如果你未改變預設設定,帶有大寫字元的這個版本將不會被匹配。如果你希望在式樣中不區分大小寫,在行模式中輸入以下命令
              :set ignorecase
              (ret)
         

    要區分大小寫,輸入

              :set noignorecase
              (ret)
         

  3. 上面的搜尋式樣肯定沒法匹配“the”在一行行末而“cat”在下一行的開頭的這種情況:
              and with Michael's careful help, we prodded the
              cat back into its cage.  Next afternoon several
         

    至於字與斷行之間是否有空格並不重要。對於一個“面向行”的編輯器而言要用找出一個式樣來匹配跨行的字串是不大可能的。

  4. 搜尋由哪個位置開始取決於你使用的編輯模式。在“可視”模式的搜尋由游標位置的下一個字元開始搜尋。“命令列”模式下搜尋由當前行的鄰近行開始搜尋。

字元。Vi/Ex有所謂的“字元”或叫做“萬用字元(wild cards)”:在搜尋中有特殊含義而不表示其字元本身。如字元“.”“*”,在

     /Then .ed paid me $50*!/
     (ret)

可用來匹配以下的任意一項:

     Then Ted paid me $5!
     Then Red paid me $5000!
     Then Ned paid me $50!

或無數的其他字串。正是字元賦予了搜尋式樣威力,但前提是必須對字元有所瞭解。

要了解這些,你得知道如何使用反斜槓()字元來控制字元“通配”功能的開與關。

許多情況下,一個搜尋式樣中的字元如果沒有前置一個反斜槓時使用的都是它的字元值――都是作為萬用字元出現的字元,而前置一個反斜槓則用來表示用作字元的字元本身。反斜槓字元本身就是一個例子,當它單獨使用時它使用的是它的字元值(即作為有“通配”功能的字元),就算在緊帖它前面的是一個普通的字元也是一樣。如果要在搜尋式樣中搜尋反斜槓這個字元則要在它之前新增一個(使用字元值的)反斜槓用來使之表示反斜槓自身。就是說要搜尋“ab”,輸入

     /ab/
     (ret)

作為搜尋式樣。如果你輸入

     /ab/
     (ret)

反斜槓會被當做一個字元也不會有什麼做用(因為 b 不是一個字元)因而這個搜尋式樣找的字串實際上是“ab”

不經常使用的字元則與此相反。這些字元如未前置反斜槓則用做一般的字元。前置反斜槓方可使用這些字元的元值。如,在

     /

中左尖括號(

     /

中它就表示一個尖括號字元。這些特殊的字元會在下面列出。

最後還有第三種型別的字元――最難搞掂的一種。一般情況下,在搜尋式樣中這些字元的元值是預設啟用的,前置一個反斜槓可讓它們做為一個普通的字元:就如同我們的第一個例子,反斜槓自身的例子。但如果你將一個名為 magic 的編輯器變數的預設值更改了,它們的工作方式恰恰相反――前置一個反斜槓來啟用字元的元值:就像我們的第二個例子,左尖括號的例子。(沒理由要將 magic 關掉。)這些最奇怪的字元也會在下面列出來。

還有不要忘了那些用來標識搜尋式樣開始或結束的標點符號,不管是斜槓問號還是其它的標點如果它也出現在搜尋的式樣中作為一個被搜尋的字元之一,那你需要前置一個反斜槓來防止編輯器將之解讀為式樣的結束符號。

字元表

.
在搜尋式樣中一個半形句號可匹配一個單一的字元,不管是字母還是數字或標點符號。事實上除了換行符(newline)外,“.”可用來匹配任意的ASCII字元。因此要查詢“default value”時考慮到它也可能被拼寫為“defaultvalue”或“default/value”又或者是“default_value”等等,搜尋式樣可以用“/default.value/”。當編輯器變數“magic”關閉時,你必須加上反斜槓讓句號使用它的元值。

*
星號,結合星號之前的一個字元,可以用來匹配由星號前的那個字元組成的任意長度的字串(包括長度為0的字串)。因此搜尋字串“/ab*c/”可以匹配“ac”、“abc”、“abbc”或者“abbbc”,依次類推。(要查詢至少包含一個b的字串,可以使用這個搜尋字串“/abb*c/”。)如果星號字元緊跟在另一字元之後,則匹配任意長度的前一字元所匹配的字元。這意味著“/a.*b/”將會找到一個在“a”與“b”之間有任意長度(包括長度為0)的任意字元的字串。當編輯器變數“magic”關閉時,你必須加上反斜槓讓星號使用它的元值。

^
當脫字元作為搜尋式樣的第一個字元時表示匹配式樣的字串必須是在一行的開頭處。它不代表在一行開頭處的任何字元(或者說它匹配一行開頭空字元)。當它不是一行開頭的第一個字元時,它表示的是一個普通的字元(未使用元值)。所以“/^cat/”會找到以“cat”開頭的行而“/cat^/”找到包含“cat^”字串的行。

$
當美元符作為搜尋式樣中的最後一個字元時表示匹配的字串必須在一行的末尾。其餘同上。

<
當一個搜尋式樣以一個反斜槓接左尖括號開始時,表示從一個“簡單”詞的開始部分開始匹配。當在式樣的其他位置出現時它並不是做為一個字元。(這個編輯器中,一個“簡單”詞可表示一個或多個數字、字母組成的字串,也可表示一個非數字或字母與任何的非空白字元組成的字串(譯者:空白字元表示空格、製表符、換頁符等等,所以“shouldn't”包括了三個“簡單”詞。)“/

>
在一個搜尋式樣的末尾一個反斜槓接右尖括號表示匹配只發生在一個“簡單”詞的末尾。其餘同上。

~
波浪線用來表示substitute命令中的後一個字串,這個命令可以在行模式下輸入或在可視模式下透過前置一個半形冒號(“:”)再輸入執行。舉例而言如果你的上一個替換命令是“s/dog/cat/”那麼“/the ~/”搜尋式樣將會匹配“the cat”。但substitute命令的輸入字串自身也能用字元如果你在之前使用其中的一個字元那麼搜尋式樣中使用波浪線會出現錯誤資訊或匹配結果你要的不一致。當編輯器變數magic關閉後,你得加上一個反斜槓才能使用它的元值。

字符集合。在搜尋式樣中還有一種字串元(多個字元組成的字元)。當多個字元使用半形方括號括起來時,可用來匹配括號中的這些字元中的任意一個。“/part [123]”將會匹配“part 1”、“part 2”或“part 3”。在關閉了編輯器變數 ignorecase (預設情形為關)後,這個功能經常用來查詢可能含有大寫的字串。輸入“/[Cc]at/”會找到“Cat”和“cat”,而“/[Cc][Aa][Tt]/”則會匹配任意的大小寫組合的“CAT”。(可用來匹配在輸入“CAT”時不小心按到Shift鍵的情況,最後一個式樣還能找到“CaT”、“CAt”等字串。)

字串元的功能還能進一步擴充套件:可在其中使用字元。如果方括號中的第一個字元是脫字元,與字符集合正好是相反的意思(即補集)。現在該字串元匹配任意沒出現在括號中的字元。式樣“/^[^ ]/”匹配未以空格開始的行。在字符集合中如果脫字元不是方括號中的第一個字元時只表示一個普通的脫字元。

方括號中的短槓(連字元)也可做為一個字元。當它出現在兩個字元中間,並且前一個字元的ASCII值要低於第二個時,它起的作用就如同你依ASCII次序由前一個字元逐個輸入字元一直到第二個字元到方括號中一樣。所以“/[0-9]%/”匹配一個數字緊接著一個“%”號的字串,它與“/[0123456789]%/”是一樣的。搜尋式樣“/[a-z]/”匹配任意的小寫字母,而“/[a-zA-Z]/”匹配任意字母,不管是大寫或小寫的字母都能匹配。兩個在方括號內使用的字元可以同時使用:“/[^A-Z]/”會匹配除大寫字母以外的任意字元。當短槓出現在括號中的第一個或最後一個位置時它沒有元值。當一個“字元-字元”的字串中的第一個字元的ASCII值比後一個字元高時,式樣搜尋時會忽略這兩個字元及其中的短槓,所以“/[ABz-a]/”與“/[AB]/”是一樣的。

在字符集合中使用反斜槓的方法要複雜一點。如果有一個右方括號做為字符集合中的一部分出現在方括號中,必須在該右方括號前面加上反斜槓。不然編輯器會誤認該括號為字符集合的結束標識。當然如果你要在字符集合中使用反斜槓你也必須在前面加上另一個反斜槓,而如果你希望括號開頭的脫字元和字元中間的短槓作為字符集合的一部分不使用它們的元值――並且也不想將它們移到這個方括號的其他位置時,你也可以在它們之前加上反斜槓。在搜尋樣式的其他位置使用作為普通字元的左方括號時可在前面加上反斜槓,否則編輯器會以為那是一個字符集合開始的標識。最後,如果關閉了magic,你在你要開始一個字符集合時你得在左方括號前加上一個斜槓。



下一篇

在這個教程的第二部分,我會透過示範如何正確地將搜尋式樣與其他元素結合來指示命令的地址,來進一步討論搜尋式樣的內容。




譯註

[1] 注意這裡的可視模式相當於Vim的一般模式和插入模式而不是Vim下的可視模式/圈選模式(也是Visual Mode)

[2] 行模式不是命令列模式的縮寫。Ed/Ex類的編輯器中編輯是面向行的,使用者使用命令對指定行進行編輯。這一類編輯器通常稱為行編輯器。Vi的行模式即是使用Ex進行編輯的一種模式

[3] Vi可以同時開啟多個檔案,會話session就是當前編輯中的所有檔案

轉自http://blah.blogsome.com/2006/06/17/vi_tut_1/

[@more@]

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

相關文章