Vim進階索引--寄存器

以前經常要安裝剪貼板的軟件來支持多次的剪切粘貼操作。現在這些步驟可以省下了,Vim的寄存器就可以當成多個剪貼板來使用。但是寄存器可不這麼簡單……

寄存器是Vim用來儲存文本的臨時空間。當我們使用y或d指令時被複制或刪除的文本會被送到寄存器,而我們可以通過p指令插入剛刪除或複製的內容。寄存器在這裏的作用就跟Window的剪貼板相似,但Vim的寄存器要更多,作用也更多。

在Vim中不同寄存器有固定的名稱。我們可以通過這個名稱訪問它們的。寄存器的名稱由單個字符組成——只有一個例外,大部分的寄存器名稱是單個數字或字母有幾個特殊的寄存器以其他字符爲名。使用時需要在寄存器名稱前加上"號以區別於一般命令以及標記(mark)。

:help registers

:help :registers

:help :copy-move

:help c_Ctrl-R

:help s\=

1 數字與字母寄存器

1.1 數字寄存器

有些寄存器是有特殊作用的如數字寄存器。在介紹數字寄存器前先看一個命令:reg。現在輸入這個命令:reg。有沒有看到許多"號開始的數字或字符呢,這些就是寄存器。這裏面有你以前刪除的文本和最近複製的文本。

寄存器"0到"9就叫做數字寄存器。寄存器"0存着上一次複製操作所複製的文本。寄存器"1到"9分別保存着你最近刪除的文本。"1的內容總是你上一次刪除的內容。每刪除一次這些寄存器的內容就往下傳遞。剛刪除的文本到了"1,而原來的寄存器"1的內容到了"2,原來"2的內容到了"3,……,原來"9的內容則被丟棄。數字寄存器只保留最近9條刪條的文本和一條複製的文本。

現在做一下下面的實驗,在Vim中分四行輸入123:

1

2

3

使用兩次dd指令,依次將1、2兩行刪除。輸入:reg觀察寄存器"1、"2。可以看到:

"1   2

"2   1

再使用dd指令將第三行刪除。再輸入:reg看看結果……現在你應該知道數字寄存器的工作方式了吧。

而我們經常使用的p指令,就是將最近一次刪除或複製的文本添加到當前位置。如果最近一次操作是複製則p就添加"0的內容,如果最近一次操作是刪除就添加"1的內容。


 

1.2 字母寄存器

現在看一下今天要講的第二種寄存器:字母寄存器(named register)。字母寄存器的名稱是單個英文字母。可以用這種方式表示一個字母寄存器:"a,"b,…,"z。同一個字母的大寫形式與小寫形式表示的是同一個寄存器,但它們在“行爲”會有所不同,這點稍後說明。字母寄存器只有在用戶指定時才被使用。

一般模式(normal mode)下要訪問寄存器只要在使用複製和刪除指令y和d時,在前面加上寄存器的名稱即可。比如要將當前行及隨後兩行(1+2=3)複製到寄存器c中:"c3yy。要將剛保存到寄存器c的內容“粘貼”出來:"cp。數字寄存器也是一樣的使用方式,要將數字寄存器3的內容粘貼出來:"3p。

提示:可是怎麼知道那個寄存器有自己想要的內容呢?使用:reg或:display。在命令後加上寄存器名稱則顯示相應寄存器的內容。

字母寄存器的名稱大寫時有特殊用途。當我們使用大寫的寄存器進行復制或刪除文本時,寄存器原來的內容會被保留,剛刪除或複製的內容則附加到原來內容的後面。如:`"Cdd‘時當前刪除行會添加到寄存器c原有內容的後面。大小寫的寄存器僅在複製和刪除時有區別。而當使用p時,大小寫寄存器名的作用是一樣的。

說明:在一般模式下Vim的刪除和複製命令相當靈活。有各種使用方式,下面是一些刪除命令的例子——將d改爲y就是複製的例子了。如

3dd

刪除3行

d2l

向右刪除2個字符

v2]d

刪除兩個段落

df。

從當前位置刪除到句號

d`c

刪除至標記(:help :mark)c

除些之外x命令和p命令也有各種形式。所有這些命令及其不同形式都可以與寄存器一起使用,方法是在d、y、x、p前面加上寄存器。篇幅所限我們不可能將與寄存器一起使用的刪除複製命令一一列舉。雖然我們舉的例子者是yy或dd這樣刪除的最簡單形式,但無特別說明在與寄存器一起使用時yy和dd可以是任何刪除、複製或是粘貼形式,以下同。詳細的刪除複製及粘貼命令可以見:help deleting :help copy-move


2 其他寄存器

除了上面的兩種寄存器外,Vim還有很多種寄存器。這是Vim的文檔中對寄存器的分類:

""

無名寄存器。保存最近一次複製或刪除的文本。就是p命令默認使用的寄存器。

"-

短刪除寄存器(The small delete register)。事實上剛刪除的文本並不一定被送到數字寄存器,如果刪除的文本不含換行符(不足一整句)則文本被送至這個寄存器。如x、d2h這兩條命令刪除的文本都會被送到這個寄存器。注意下在這條命令雖然刪除了一整行的文本但因不含換行符所以也被送到這個寄存器`0d$‘。

": ". "% "#

只讀寄存器。它們分別用來保存最近一次在命令行窗口使用的命令、最近一次插入的文本、當前編輯的文件名、當前的替代文件名。

"=

表達式寄存器。

"* "+ "~

選擇與拖放的寄存器。在Windows中這幾個寄存器就是剪貼板。在Linux中它們也是剪貼板——但這幾個寄存器是有所區別的。

"_

黑洞寄存器
刪除操作會影響現有數字寄存器的內容。前一個數字寄存器的值傳給後一個數字寄存器,"9的內容被丟棄,新刪除的文本則放入"1。這至少有兩個直接的影響,一是"9的內容被丟棄;二是寄存器中文本的位置都發生了變化。而複製操作會改變"0的值。如果你不希望刪除或複製的操作影響數字寄存器的話就使用這個寄存器。使用這個寄存器進行刪除或複製的內容都會被丟棄——這還可以提高一點速度節省一點空間。

"/

搜索式樣寄存器。保存上一次搜索所使用的式樣。注意這也包括了s命令中所使用的搜索式樣。


 

3 寄存器相關命令

前面已經說了一般模式下下的各種x、d、y、p命令都可以與寄存器一起使用,如"ayy。現在看一下在命令窗口中(或Ex模式)下怎麼訪問寄存器。
命令行中複製、刪除和粘貼分別是`:y‘、`:d‘、`:pu‘。寄存器的使用方式是直接在上述命令後面加上寄存器的名稱——不需要在寄存器前加入"號。如:

:2,4y a

將第2至4行的文本複製到寄存器a中。

:'<,'>d A

將選中的行刪除並將其內容附加到寄存器a中。

:pu! a

將寄存器a的內容粘貼到當前行之前。

寄存器也是一種變量可以在表達式中使用,因而也經常配合:exec構造複雜的命令。在替換命令:s的替換式樣部分可以使用表達式寄存器(:h sub-replace-expression)。一般模式命令q(:help q)可以用來錄製宏,而所錄的擊鍵序列就保存在寄存器中。此外:redir命令也可以與寄存器一起使用。具體見Vim文檔(:help :redir)。

4 寄存器的特殊性質

我們已經知道數字寄存器可以保留複製和刪除的內容供用戶使用。但這個功能實際用途已大不如前,因爲Vim支持無限的undo操作。而且由於引入了Vim腳本後可以使用變量來代替寄存器的作用。但寄存器並非毫無存在價值。


4.1 是臨時的存儲空間

這正是寄存器出現的目的。有時候我們需要一些臨時存儲空間我們就可以使用寄存器而不需要新建一個臨時文件。比如寫作時你也許會發現有一整段的文字也許應該刪除或放到其他位置。這時你可以把它放到寄存器中。然後在需要時再把它貼出來——沒錯就象Windows的剪貼板。但更好用,因爲你有26個字母寄存器可以使用;可以使用大寫字母將文本附加到已有內容後。如果在你關閉文件之前還沒想到這將這些內容貼在哪裏也沒關係用`:wviminfo my_viminfo‘命令。下一次編輯時輸入`:rviminfo! my_viminfo‘或者在命令行用這個命令運行`gvim -i my_viminfo myfile‘,:reg看寄存器的內容是不是都還在呢1

4.2 寄存器也是變量

在上一篇“腳本”中我們說過了是個變量——特殊的變量,只要在前面加上一個@號就可以用變量的方式訪問寄存器。
所以,變量的操作也同樣適用於寄存器。

" 給寄存器賦值

let @e="開始\<CR>"

let @E="結束"

echo @e

開始

結束

" 將寄存器作爲表達式的一部分

let my_var=@a . @c

" 和

echo @e+4

" 清空寄存器。

" 注意:不能用unlet清除寄存器。

:let @e=""

4.3 在編輯窗口與命令窗口間交換內容

編輯窗口的文本可以放進寄存器。搜索式樣和上一條Ex命令被放進了只讀寄存器"/和":。
已知寄存器的內容可以在貼到編輯窗口。可以在命令窗口作爲變量使用。那有沒有辦法在命令窗口插入寄存器的內容呢?有沒有辦法在搜索式樣中插入寄存器的內容呢?

比如,假設在寄存器e中保存着一個文件名:“這是一個保存在寄存器中的很長的文件名.txt”。而我想使用:w命令保存一個當前編輯文件的副本——使用寄存器e中的那個文件名。如果使用`:w @e‘的話,文件名將是“@e”而不是“這是一個保存在寄存器中的很長的文件名.txt”。這時該怎麼辦呢?考慮到寄存器也是變量,我們可以使用寄存器的傳統辦法。

" 方法一。使用:execute命令

" 寫入以"e爲名的寄存器中

:exe "w " . @e

那搜索呢?如果我們要在搜索式樣中使用寄存器的內容呢?對於s命令的搜索式樣上面的:exe大法仍然適用,但如果只是普通的搜索操作(在一般模式中按/)呢?我們要用到組合鍵Ctrl-R,用Vim的寫法就是<C-R>。

" 方法二。使用Ctrl-R轉義。

" 搜索寄存器e的內容。<Ctrl-R>表示用戶在這裏按了組合鍵Ctrl-R——不要直接輸入<Ctrl-R>這8個字符。

/<Ctrl-R>e/

使用<C-R>的方式可適用於各種輸入的環境中:在插入模式輸入時、在命令窗口輸入時、在搜索時。在插入模式時要輸入寄存器內容並不需要退回到一般模式再使用p指令,可以直接按`<Ctrl-R>e‘當然e可以改成相應的寄存器名。在命令窗口與搜索時也是一樣:按Ctrl-R輸入寄存器名。

提示:除了一些不接受變量作爲參數,不能使用寄存器名稱的情況外,還有一些情況也要求插入寄存器的內容。有時我們插入寄存器的內容而不使用寄存器變量是因爲我們可能還需要手工對寄存器的內容進行一些編輯。

無名寄存器總是保存着最近一次複製或刪除的內容。不帶寄存器名地使用p就可以添加該寄存器的內容到當前位置了。但是既然“無名”該怎麼在命令窗口使用這個存器呢?又怎麼插入無名寄存器的內容呢?答案是使用@",插入也是一樣按Ctrl-R再按輸入"就可以了。

現在總結一下:":保存了上一條Ex命令。"/保存了上一條搜索式樣。字母寄存器及數字寄存器中可以保存編輯的文本。並且我們也可以在不同的環境中插入寄存器的內容。通過寄存器我們可以方便地在命令窗口編輯窗口以及搜索中交換內容。相對而言一般的變量就沒這麼方便,你只能在命令行中使用變量也只能是命令行中給變量賦值。


4.4 在buffer之間及程序之間交換內容

寄存器是全局的變量。在Vim中打開的所有文件2,共享這些寄存器。你可以在不同的文件之間交換內容。

通過寄存器"*和"+,Vim可以與其他程序交換信息。在Windows中這兩個寄存器是一樣的。在Linux中這兩個寄存器則有所不同。

:help gui-selections

:help x11-selection

4.5 寄存器可以做爲宏

跟一般的變量相比寄存器還有一個最大的特點就是寄存器本身可以做爲宏使用。如果你有用過一般模式命令q的話就會發現q錄製的擊鍵序列就是存在寄存器中的,並且可以直接使用寄存器執行命令。現在做做實驗,新建一文檔隨便輸入幾行文字。輸入:

qeggddq

上面這條命令錄製了一個宏並保存到寄存器e中。這個宏的作用是回到第一行並刪除該行。現在看一下寄存器的內容:

:reg e

就是你剛纔的鍵盤命令ggdd。要運行剛錄製的鍵盤操作在一般模式輸入@e就可以運行了,輸入3@e會將前三行刪除。

當然你不一定要用q來錄製宏——因爲寄存器也是變量。

:let @e="/刪除本行/^Mdd:w^M"

@e

上面的^M表示的是回車鍵。可不是輸入^再輸入M,而是輸入Ctrl-V(Windows是Ctrl-Q)再按回車鍵這時就會出現^M表示這是一個回車鍵。常見的還有^[表示的是<ESC>鍵。輸入的方法也是一樣按Ctrl-V再按Esc鍵。這樣輸入控制字符的方式是傳統的Vi方法。在Vim中也支持用按鍵名錶示這些控制字符。比如<CR>表示回車鍵3所以上面的命令也可表示爲:

:let @e="/刪除本行/\<CR>dd:w\<CR>"

這裏一定要用雙引號,我們在“腳本”一篇中已經講到了,在單引號中的字串會被當成普通字串。後面這種表示控制字符的方式與'cpoptions'的設置有關,雖然在默認情況下都是可行的但是建議使用第一種方式。不過爲了更好的可讀性在教程中我們還是可能使用後面這種方式表示控制字符。

正因爲寄存器可以直接執行所以":可以用來執行上一條在命令窗口使用的命令:

:@:

記得最後要按回車執行。當然現在由於命令行的歷史功能這種用法沒有什麼實用價值。

關於宏的更多用法我們將另外解說。

4.6 在重定向命令中使用

重定向命令(:redir)是一個較常用的技巧。所有的字母寄存器、@*、無名寄存器(@")都可以在重定向命令中使用。還是用個例子說明好了:

假設你的小說家朋友寄了一本小說的初稿給你,但顯然他沒有整理文本的習慣——好消息是他這次竟然沒用Word寫。在你往下看之前你決定先將文檔做適當的整理。使用Vim作這種事當然是小菜一碟,只用了10分鐘你就將他的小說整理成一份格式整齊的文檔了。

第六章 爲山九仞

===============

       

  小明是從不在午時之前離開被褥的,今天卻是個例外。他一夜沒睡不

  過他卻覺得精神比任何時候都好……

       

  < 省略800行 >

  ……

       

第七章 功虧一簣

===============

       

  小明已經很久沒像今天這樣開心了。從那時到現在已有二十年又一天

  了。對他來講二十年並不長,能在二十年又三天之內報仇已經是出乎

  自己意料了。何況對方是可是威巒鏢局的大當家。想到這裏他的眼睛

  眯得更小了……再過兩天……只要兩天!

       

但你發現這份初稿沒目錄,而你看小說的習慣是從目錄看起。於是你決定整理一份目錄。於是你用了寄存器:

:let @a=""

:g/^第.\{1,3}章 /y A

這兩條命令將所有章的標題放到寄存器A中。你可以在需要目錄的地方"ap。不過你還想在每章標題後加上該章對應的行號,你知道這時可以用:redir:

:redir @a

:echo "目錄:"

:g/^第.\{1,3}章 /echo getline(".") . "\t\t\t" . line(".")

:redir END

現在你的寄存器a中有了一個帶行號的目錄了。只用了幾行命令你就漂亮地完成任務了,想到這這裏你的眼睛眯得更小了……

注:這裏用到的函數是我們在講摺疊時說過的getline("."),表示返回當前行。line(".")則返回當前的行號。這兩個函數的詳細用法見文檔。通過對這個腳本進行擴充我們甚至可以讓它抓取含小節的目錄。

上面的例子演示了通過:redir用戶能對寄存器的內容進行進一步的加工而不只是簡單的摘錄,它增加了寄存器的使用範圍。這正是與redir之所以成爲寄存器重要性質之一的重要原因。在Vim7.0之前的版本中不支持重定向內容到變量,所以寄存器成了唯一選擇。考慮到:redir是比較重要的命令,寄存器吃香也就不足爲奇了。但在Vim7開始支持重定向內容到變量後,寄存器就沒那麼重要了——當然如果你希望方便地將重定向的內容插入到文件中的話寄存器仍是理想之選。關於:redir的更多內容,將會另外解說。

4.7 表達式寄存器

雖說是寄存器但從各種角度來看這都是個冒牌的寄存器。它的主要作用是實時計算表達式的值。適用的場合:在編輯輸入時、在命令窗口輸入時、在搜索時。使用的方式是按Ctrl-R再按等號(<C-R>=),接着輸入表達式,原來輸入的位置就會插入表達式的值。只要是合法的表達式都可以使用。我們知道字串可以做爲合法的表達式,所以在插入模式下按Ctrl-R =然後輸入"abc"(注意包括")當前位置就插入了abc。當然我們不會爲了輸入字串而使用這個寄存器。現在寄存器a中保存着一個數字,你想在當前文檔中搜索該數字4倍的另一個數,你當然不想自己計算。這時使用表達式寄存器:/<Ctrl-R>=@a*4<Enter>/&lt;Enter> 。其中<Ctrl-R>不是讓你輸入這8個字符而是按組合鍵Ctrl-R,同理<Enter>表示這裏按了回車。任何時候當你需要插入一個表達示的值時都可以使用這個寄存器。

如果在輸入=號後直接按回車沒有輸入表達式的話默認使用上一次使用的表達式。

在上一個例子中,如果你把剛纔的目錄貼在文件的開頭(當然是開頭),會發現行號不準了因爲所有的內容都被往下移了——第一行現在變成在目錄後面了。假設增加的目錄有25行(不知道有幾行?:se nu),現在文章的第一行(是空行)成了第26行。當然這樣問題難不倒你,讓表達式寄存器重新計算一下行號就行了——將原來的行號加上25。

注:下面幾個控制字符的輸入方式:^I, ^R, ^M, 分別表示的是Tab鍵,Ctrl-R,回車。它們的輸入方式是按Ctrl-V(或Ctrl-Q)再輸入各自所表示的鍵。

:1,25norm $T^I"ty$:s/[0-9]\+$/^R=@t+25^M/^M

:1,25norm                                                 在1到25行之間(目錄區)執行一般模式命令

          $T^I                                             移到行末,將光標定位到最後一個製表符後(也就是第一個數字的位置)

              "ty$                                         將數字複製到寄存器t中

                  :s/[0-9]\+$/                             將行末的數字(每一章的行號)

                              ^R=                          插入表達式

                                  @t+25                    將寄存器t中的數字加上25

                                     ^M                    插入回車結束表達式

                                         /^M               結束s命令並在插入回車鍵

                                            <Enter>        在全部輸入完成後別忘了按回車執行命令

還有一個特殊的地方可以用上表達式寄存器(Vim文檔沒說這是一個表達式寄存器,但它的使用方式與表達式寄存器完全一樣),就是:s命令。:s命令的命令格式爲::s/lhs/rhs/,表示搜索lhs並替換爲rhs。一個特殊用法就是當rhs的開頭爲`\=‘時,這rhs將被視爲表達式。lhs將被替換爲表達式的值。

:" 例:將當前行中的算術式'42x31'替換爲算術式的結果。

:s/42x31/\=42*31/

再回到剛纔的例子,中現在我們可以用一種相對優雅的方式計算更新該目錄的行號:

:1,25s/[0-9]\+$/\=submatch(0)+25/

注:submatch()只在:s命令rhs的表達式中使用。submatch(0)與原來的&在rhs的作用是一樣的。submatch(1)就相當於原來rhs中的\1,依此類推。

5 小結

當我們在進行交互編輯時,寄存器可以提高效率。不過正因爲它的這樣交互特性,它並不經常在腳本中使用。因爲我們完全可以在腳本中使用變量來替代寄存器。它的優點是能方便地與buffer的內容互動。當我們想把buffer的內容賦予某個變量時比較麻煩因爲沒有直接的一般模式命令可以做到這時寄存器則更方便點。


 

Footnotes

[1] 通常寄存器的內容會保留到下一次打開Vim,但不一定會保留到下一次打開同一文檔所以需要人爲的保存
[2] 準確地說是buffer,這與文件的概念並不完全一樣
[3] :help key-notation

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章