Vim 對特定行處理常用方法(三):奇偶行分離(及寄存器入門)

目錄

  • 奇偶行刪除
  • 合併奇偶行

  • 奇偶行分離(及寄存器入門)(本文)
  • 刪除、壓縮重複行

  • 跨行操作及長文本分割基礎

3. 奇偶行分離

** 本篇命令 **nn:g/^/m$ 最簡單的方式,奇偶行將會分為連續的兩組,適用於臨時需要的手n 動操作。應用時要確保正文後面不為奇數空行。nnlet @a="" 把寄存器 a 清空,以使用下述命令。你也可以使用其他不同寄存n 器。但如果你要把內容*自動*放到剪貼板,情況就會比較複雜,更n 適合通過腳本來操作。這會在正文稍微提及。nn:g/^/+d A 把偶行刪除並貯存到寄存器 a 中。使用時需要把內容從寄存器 an 中提取出來。n:g/^/d A|m. 把奇數行存在寄存器 a 中。nn:%norm j"Add 把偶行刪除並貯存到寄存器 a 中。nn:%norm jk"Add 把奇行刪除並貯存到寄存器 a 中。nn:g/^/y A|+m. 一個無損的方法,只是把奇數行複製到寄存器 a 中。nn:g/^/+y A|+m. 只是複製偶數行到寄存器 a 中。nn:let @+=@a 把寄存器內容放到剪貼板。nn** 本篇幫助 **nn:h :yn:h :registersn:h copy-moven:h letn:h let-@n:h quote_alphan:h quotestarn:h quoteplusn

這一篇,通過前面兩篇基礎的積累,我們可以來進行更複雜的操作——分離奇偶行。

奇偶行的分離,顧名思義,就是把奇數行的內容和偶數行的內容分離開來,方便我們分別利用。通過前面的知識,實際上我們也能夠用刪除的方法,分別保留奇數行和偶數行的內容,以達到同樣的目的。但是,這種方法終究是不夠直接的曲線救國,所以在這一章,我們就來看看有什麼方法,可以一次性的把奇偶行分離開來。

3.1 基本操作

事不宜遲,讓我們來看看這一篇要用到的實例,《聖經》中的一部分:

01| 1:1 起初神創造天地。n02| 1:1 In the beginning God created the heaven and the earth.n03| 1:2 地是空虛混沌。淵面黑暗。神的靈運行在水面上。n04| 1:2 And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.n05| 1:3 神說,要有光,就有了光。n06| 1:3 And God said, Let there be light: and there was light.n07| 1:4 神看光是好的,就把光暗分開了。n08| 1:4 And God saw the light, that it was good: and God divided the light from the darkness.n09| 1:5 神稱光為晝,稱暗為夜。有晚上,有早晨,這是頭一日。n10| 1:5 And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.n11| 1:6 神說,諸水之間要有空氣,將水分為上下。n12| 1:6 And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.n13| 1:7 神就造出空氣,將空氣以下的水,空氣以上的水分開了。事就這樣成了。n14| 1:7 And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.n

這是奇偶行文本的一種典型例子:雙語對照文本。在學習、練習翻譯之類的需求中,我們會希望把中英內容分離開來。

我們可以有一個極簡單的方法:

:g/^/+m$n

可以得到如下文本:

01| 1:1 起初神創造天地。n02| 1:2 地是空虛混沌。淵面黑暗。神的靈運行在水面上。n03| 1:3 神說,要有光,就有了光。n04| 1:4 神看光是好的,就把光暗分開了。n05| 1:5 神稱光為晝,稱暗為夜。有晚上,有早晨,這是頭一日。n06| 1:6 神說,諸水之間要有空氣,將水分為上下。n07| 1:7 神就造出空氣,將空氣以下的水,空氣以上的水分開了。事就這樣成了。n08| 1:1 In the beginning God created the heaven and the earth.n09| 1:2 And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.n10| 1:3 And God said, Let there be light: and there was light.n11| 1:4 And God saw the light, that it was good: and God divided the light from the darkness.n12| 1:5 And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.n13| 1:6 And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.n14| 1:7 And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.n

英文和中文的部分分離開來了。那麼這個命令是如何生效的呢?

+m$ 命令是把 global 命令所在行的下一行移動到最後一行, 因此當 global 命令在第一行運行的時候,第二行的英文就會移動到最後一行,原本第三行的中文成為新的第二行。global 命令執行到新的第二行的時候,就會把新的第三行的英文又移動到最後一行。

所以到了最後,中英文就會全部分開。

在開篇中,我提到了這個命令使用時要注意正文後不能有奇數空行,道理很簡單,因為空行是會被執行的,所以執行到最後一個奇數空行的時候,奇數空行的下一行正好就是偶數文本的第一行。這樣偶數文本第一行就會被移到最後去了。

假如原本的文本中最後一行是空行,● 位置就是 global 正在執行的位置,09 行n正好是原本偶數行的第一行。此時 global 執行 +m$ 命令,09 行就會被移動到n15 行去nn 01| 1:1 起初神創造天地。n 02| 1:2 地是空虛混沌。淵面黑暗。神的靈運行在水面上。n 03| 1:3 神說,要有光,就有了光。n 04| 1:4 神看光是好的,就把光暗分開了。n 05| 1:5 神稱光為晝,稱暗為夜。有晚上,有早晨,這是頭一日。n 06| 1:6 神說,諸水之間要有空氣,將水分為上下。n 07| 1:7 神就造出空氣,將空氣以下的水,空氣以上的水分開了。事就這樣成了。n● 08|n 09| 1:1 In the beginning God created the heaven and the earth.n 10| 1:2 And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.n 11| 1:3 And God said, Let there be light: and there was light.n 12| 1:4 And God saw the light, that it was good: and God divided the light from the darkness.n 13| 1:5 And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.n 14| 1:6 And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.n 15| 1:7 And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.n

當然,利用這點,我們也能作出更好的奇偶行分離。

在上面的例子中,分離了的中英文內容是緊貼在一起的,這樣對於下一步的操作很不方便。有沒有辦法在中英文內容中間加個空行呢?那就是在操作前,給文本末尾加上兩個空行。如果要在中間插兩個空行,那就加上四個空行。

原理很簡單,奇數的空行會讓文本內容發生我們不希望的移位,但如果是偶數的空行,那麼發生移位的就只會是空行而已,並不會影響到文本內容。

如果你已經對 global 命令比較熟練,那麼也很容易能夠設計出這樣的命令,完全不受文末空行的影響:

:g/^S/+m$n

奇偶行內容分開之後,就可以輕鬆選中目標內容,複製到剪貼板,再粘貼到需要的地方:

注意,下述操作適用於奇偶行文本有空行分隔的情況nnV}k"+y 標準操作 nV}k<C-C> 一些 GUI 版本 vim 中,支持 ctrl+c、cmd+c 之類的操作,或者你n 可以自行設置快捷鍵綁定。 n

3.2 結合寄存器

3.2.1 寄存器入門

在第一篇中,我提到了 vim 的三個核心競爭力:ex 命令、寄存器和 vim language。ex 命令在上兩篇已經演示了很多, vim language 就是用來寫 vim 腳本的,這兩者的威力不言而喻。但有的朋友會奇怪,為什麼沒有提到宏,很多 vim 的文章中都提到宏是 vim 的一個很強大的功能。其實只要各位看一下宏的幫助就明白了:

:h qn:h @nnq{0-9a-zA-Z"} 在寄存器 {0-9a-zA-Z"} 里記錄鍵入的字元 (大寫名字的寄n 存器表示附加鍵入的內容)。q 命令不能在執行寄存器時使n 用。同樣,在映射里也不能。nn@{0-9a-z".=*} 執行寄存器 {0-9a-z".=*} 的內容 [count] 次。 注意 不n 能用寄存器 % (當前文件名) 和 # (輪換文件名)。n "@="則會提示你輸入一個表達式。這個表達式的結果會被執行。n

因此,宏僅僅是寄存器的一種應用而已,我們不光可以通過 q 命令記錄操作到寄存器,還可以直接為寄存器賦值,然後用 @ 命令來執行。

剛剛使用過的命令、函數、操作,也會記錄到不同的寄存器,可以通過 @ 來重複執行。所以,知道宏的實質,了解寄存器,是讓你的 vim 更強大的必經之路。

現在,就讓我們來看看什麼叫做寄存器。

所謂的寄存器,實際上就如其名,是一個用來貯存內容的容器。操作系統的剪貼板是一種寄存器,你複製了一段文字,這段文字就會放到剪貼板中,然後你就可以在不同的地方粘貼使用。

vim 的寄存器也有這樣的基本功能,你可以把一堆字元通過刪除、複製的方式放到寄存器中,然後在其他地方拿出來用。當然,vim 的寄存器和操作系統的剪貼板有很多不同的地方。

vim 總共有 9 種寄存器,各位可以通過幫助來詳細了解,這裡就不贅述。

:h registersn

vim 的移動、刪除和複製文本命令都能夠配合寄存器使用。在 normal 模式下,可以通過半形雙引號「 " 」來指定,而在 ex 命令中,則可以在命令後面使用(要以一個空格分隔)。比如 ydt

:h copy-moven

本篇中,我們要用到的就是「命令寄存器」( a-zA-Z ):

:h quote_alphan

3.2.2 存入寄存器

上面的命令很簡單,處理一些臨時的需求很方便。但是,如果你需要在一些宏或者腳本中用到,上面的方法就顯得啰嗦和麻煩了。通過寄存器,我們可以讓這個操作一步到位——分離之後,就你可以把內容貼到你需要的地方了。

在開始之前,我們要先把命令寄存器清空:

:let @a=""n:h let-@ n

我們接下來要通過追加內容的方式來使用寄存器,如果寄存器中原本就有內容的話,就會污染我們的目標文本,所以清空寄存器是必須的。

下一步,我們把奇數行或偶數行的內容刪除,並置入寄存器中 a 中:

:g/^/+d An:g/^/d A|m.n

這兩個命令各位應該很熟悉了,和我們刪除奇偶行用的命令很相似,不同的地方只是在於,位於刪除命令 d 的後面,有個大寫字母 A,意思是把刪除的內容追加到寄存器 a 中。

A 是 26 個命令寄存器之一,與 a 對應。實際上命令寄存器只有 a-z 這 26 個是可以被調用的,A-Z 的這些使用大寫名字的寄存器,全部都和 a-z 一一對應,表示把內容追加到寄存器。

比如下面這個命令

:g/^/+d a n

意思是把每一行刪除的內容都放到寄存器 a 裡面,新的內容會覆蓋掉舊的內容。而這條命令

:g/^/+d An

則是把刪除掉的行追加到寄存器 a 中,新的內容會跟在舊的內容後面,並不會覆蓋舊的內容。

當提取寄存器內容時,調用 a 和調用 A 是沒有差別的。各位可以在操作完成之後把寄存器 a 中的內容粘貼出來看看差別。normal 模式下

"apn"Apn

插入模式下

<c-r>an<c-r>An

而具備這種追加內容特性的就只有命令寄存器,其他寄存器都不具備追加功能。

同樣地,我們可以通過 normal 命令來完成這個工作:

:%norm j"Addn:%norm jk"Addn

這兩個命令和之前刪除奇偶行的命令也幾乎是一樣的,僅僅是增加了「 "A 」。A 是命令寄存器,而 " 則是「指定寄存器」的意思,在 3.1 中已有說明。

上述的操作將會破壞原文檔,如果我們只是希望提取出奇偶行的內容,那麼就應該使用複製的方法:

:g/^/y A|+m. "把奇數行複製到寄存器 a 中n:g/^/+y A|+m. "把偶數行複製到寄存器 a 中nn:h :yn

這兩條命令中使用了 y 代替 d ,這樣原文檔就不會被修改。但是,這樣 global 命令就無法跳過不必要的行,所以添加了 m. 來移除非目標行的標記,這個技巧已在上一篇中有詳細說明。

但是,這種需求就難以用 normal 命令來實現了。

3.2.3 提取寄存器

現在,我們已經把需要的文本放進了寄存器中。在 vim 內部,你有 3 種常用的方法來提取寄存器內容,適用於不同場景,分別是:

"ap normal 模式下粘貼寄存器內容,有多種變體,具體參照 :h p n

<C-R>a 插入模式下粘貼寄存器內容,參照 :h c_<C-R>nn:put a 通過命令在指定行中插入寄存器內容,默認是當前行,參照 :h :putn

上述 3 種方式都可以讓你方便的在 vim 打開的文本中提取寄存器的內容,而且只要寄存器不清空,你在任何時候都能夠取回其中的內容。

但是,這往往不能夠滿足我們的需求。我們提取的內容可能會希望粘貼到網上、word 中、筆記軟體中,這個時候,就繞不開系統的粘貼板,或者叫剪貼板。

vim 中有兩個寄存器和剪貼板相關,分別是 +* ,稱為「選擇寄存器」。在不同的操作系統中(比如 Windows、Linux、OS X )這兩個寄存器的運作會有不同。但是,在這裡,我們不需要糾結它們技術上的差異,只需要知道哪個可以正常工作就行了(比如在 Windows 中,兩者是等價的,根據我自己的測試,在 OSX 上也是一致的)。

有興趣的朋友可以參照它們的幫助說明:

:h registers 參看第 7 點,選擇寄存器 n:h quotestar * 寄存器n:h quoteplus + 寄存器 n

在接下來的講解中,我會使用 + 寄存器,如果你發現這個寄存器無法正常運作,可以改用 * 試試。

和命令寄存器不同,選擇寄存器無法直接追加內容,每次把新的內容放入選擇寄存器,舊的內容都會被覆蓋。所以我們無法通過改造上面給出的方法,讓目標內容直接放入選擇寄存器。

這個問題有兩個解決方案:

  1. 把完成收集命令寄存器內容放到選擇寄存器中;
  2. 通過變數和表達式在選擇寄存器中追加內容。

方案 1 簡單,只要一個很易懂的命令:

:let @+ = @an

有編程基礎的朋友應該可以直接理解這條賦值語句,意思就是讓寄存器 + 的內容等於寄存器 a 的內容。因為 + 會被 a 直接取代,所以不必事先進行清空。

方案 2 在這個需求中使用則顯然過重了,這裡僅列出來,供各位參考,在未來其他系列的文章中,我們也許就會用到這種方案。

:let @+="n" 選擇寄存器是無法清空的,它總與系統剪貼板同步,所以你需要用一n 些替代掉系統剪貼板的舊內容,又不會干擾到稍後追加的內容,換行n 符是個不錯的選擇。nn:g/^/let @+.=getline(".")|+m. 奇數行內容追加到剪貼板n:g/^/let @+.=getline(line(".")+1)."n"|+m. 偶數行內容追加到剪貼板nn:h let.= let 的一種簡略寫法,let a.=b 相當於 let a = a . bn:h expr5 vim 表達式的語法說明,「 . 」在表達式中用於連接字元串n:h getline() vim 內建函數,用於取得制定行號的行的內容n:h line() vim 內建函數,用於取得指定位置的行號,可配合 getline 使用n

那麼,本篇的內容就到此告一段落,在下一篇中,我們將會開始了解 {pattern} 的相關內容。

本篇命令及幫助回顧

** 本篇命令 **nn:g/^/m$ 最簡單的方式,奇偶行將會分為連續的兩組,適用於臨時需要的手n 動操作。應用時要確保正文後面不為奇數空行。nnlet @a="" 把寄存器 a 清空,以使用下述命令。你也可以使用其他不同寄存n 器。但如果你要把內容*自動*放到剪貼板,情況就會比較複雜,更n 適合通過腳本來操作。這會在正文稍微提及。nn:g/^/+d A 把偶行刪除並貯存到寄存器 a 中。使用時需要把內容從寄存器 an 中提取出來。n:g/^/d A|m. 把奇數行存在寄存器 a 中。nn:%norm j"Add 把偶行刪除並貯存到寄存器 a 中。nn:%norm jk"Add 把奇行刪除並貯存到寄存器 a 中。nn:g/^/y A|+m. 一個無損的方法,只是把奇數行複製到寄存器 a 中。nn:g/^/+y A|+m. 只是複製偶數行到寄存器 a 中。nn:let @+=@a 把寄存器內容放到剪貼板。nn** 本篇幫助 **nn:h :yn:h :registersn:h copy-moven:h letn:h let-@n:h quote_alphan:h quotestarn:h quoteplusn

推薦閱讀:

TAG:Vim | 文本编辑器 | vimL |