萬行以上文本的分組編號——在 VIM 中嵌入 python 和 perl 腳本

閱讀前請注意:

本文僅藉此實例介紹如何在 vim 中使用 python 和 perl 腳本,不代表 vim 在處理格式化的較大文本時是一個好的選擇。

對於行數較多的格式化文本(比如 log 文件),還是建議使用專門的處理工具或者通用腳本語言。

常見的通用腳本語言:perl、python;

Windows 可選方案:batch(批處理)、powershell 腳本、vbs;

*nix 可選方案:sed、awk、bash shell(建議配合前兩者使用)。

早前,@楊君 朋友提出了一個問題:

vim怎麼匹配多個相同字元並替換成字元加數字遞增的形式? - 編程 - 知乎

我提供了一個使用宏的解決方案。此後在與楊君的交流中,得知楊君需要處理的文本行數稍多(數千行),這個數量已經超出宏的處理能力(並非不能做,只是速度太慢,無法接受),我後來建議他用其他工具處理。

實際上 vim 的宏只適宜臨時處理很小的文本,面對行數較多的文本效率會明顯下降。特別是在家用電腦的環境下,用宏或者命令來處理幾千 k 的文件不是個好主意。

在本文中,我準備了如下測試文檔,共 700007( 70 萬零 7 行),文件大小 8.0M:

1| "value"n 2| {n 3| "wave"n 4| "wave"n 5| "wave"n 6| "wave"n 7| }n 8| "value"n 9| {n 10| "wave"n 11| "wave"n 12| "wave"n 13| "wave"n 14| }n 15| "value"n 16| {n 17| "wave"n 18| "wave"n 19| "wave"n 20| "wave"n 21| }n...n700001| "value"n700002| {n700003| "wave"n700004| "wave"n700005| "wave"n700006| "wave"n700007| }n

目標是對每組的 "wave" 進行獨立的編號:

1| "value"n 2| {n 3| "wave0"n 4| "wave1"n 5| "wave2"n 6| "wave3"n 7| }n 8| "value"n 9| {n 10| "wave0"n 11| "wave1"n 12| "wave2"n 13| "wave3"n 14| }n 15| "value"n 16| {n 17| "wave0"n 18| "wave1"n 19| "wave2"n 20| "wave3"n 21| }n...n700001| "value"n700002| {n700003| "wave0"n700004| "wave1"n700005| "wave2"n700006| "wave3"n700007| }n

測試環境(我自己的電腦)情況如下:

1. 宏

在前面提到的問題中,我採用的方案是:

qan:let i=0nvi{n:g/"wave"/s//="wave . i . "/|let i=i+1nqn/"value"n:%norm j@an n

你將發現 vim 會彈出反饋信息,讓你選擇繼續操作還是停止。此時處理量已經超出 vim 的正常處理能力。如果你選擇通過 silent 調用命令,以無視信息

:silent! %norma j@an n

vim 將會默默進行處理,不會再有任何反饋,就好像卡死了一樣。它可能是真卡死了,也可能只是還在處理,你無從得知。我昨晚試過放了 20 分鐘也還沒有反應。因此在這種情況下,用宏絕對不是個好主意。

2. vim language

在 vim script 裡面跑的那個語言就是 vim language( VimL )。你能夠像使用其他編程語言一樣,用 VimL 來寫程序。我簡單地寫了一個:

function! RenameByVim()n echo("開始處理...")n let start_time = localtime()n let lnum = 1n let c = 0n let num = 0n while lnum <= line("$")n let con = getline(lnum)n if con == "value"n let num = 0n elseif con == "wave"n call setline(lnum, "wave" . num . ")n let c += 1n let num += 1n endifn let lnum += 1n endwhilen echo("完成!n共替換 " . c ." 行n耗時:" . (localtime()-start_time) . " 秒")nendfunctionn

處理結果如下(界面尚未刷新,按下回車才能看到結果):

9 秒!替換 40 萬行居然要 9 秒!雖然這個函數寫得很粗糙,還有優化的空間,不過 VimL 的效率一向是很低的,優化恐怕也好不到哪裡去,為什麼不試試更好的呢?

3. python

python 是門很好入門的腳本語言,特別是對於我這種非科班出身、只想做些文本出力的人。不過這裡我們不講 python,只是演示一下怎麼在 vim 裡面用 python。

:help python " 這個幫助相當詳細,足夠你了解n

3.1 安裝 python

首先確保你的 vim 有編譯 +python 特性:

:versionn" 看看裡面有沒有 +python/dyn +python3/dyn 。n" 如果沒有,通過搜索引擎找一個編譯了 +python 特性的版本。vim.org 官網提供的版本n" 就有編譯此特性。你也可以自己編譯一個,不過你都能自己編譯了,還看這個文章幹什麼呢?n

然後,裝一個 python 。這裡需要注意的是,留意你的 vim 是 32 位還是 64 位的。32 位的 vim 需要配合 32 位的 python,64 位的 vim 要用 64 位的 python。裝完之後看看是不是成了:

:echo has("python") " 顯示 1 就成功n:python print "Hello Vim"n

如果沒成,報類似錯誤(此為 Windows 下錯誤):

E370: Could not load library python27.dllnE263: Sorry, this command is disabled, the Python library could not be loaded.n

Windows 下解決方案是到 python 安裝文件夾找到 python27.dll( 27 為版本,指 2.7 ,這個可能是其他數),複製到 vim 安裝目錄(和 vim.exe 同一目錄)下即可。

Linux 我沒有用過。macOS 未遇到過錯誤,無法提供參考。

3.2 嵌入 python 代碼

一切準備就緒,我們來寫解決方案了。

在你的 vimrc 中寫如下函數(如要獨立弄一個腳本文件,請參考:生成鏈接序列並複製到剪貼板——自定義函數入門)

function! RenameByPythonV1()n let start_time = localtime()n echo("開始處理...")npython << EOFnimport vimnvimBuffer = vim.current.buffernnum = 0nc = 0nl = 0nwhile l < len(vimBuffer):n if vimBuffer[l] == "value":n num = 0n elif vimBuffer[l] == "wave":n vimBuffer[l] = "wave + str(num) + "n c += 1n num += 1n l += 1nvim.command("let c=" + str(c))nEOFnecho("完成!n共替換 " . c ." 行n耗時:" . (localtime()-start_time) . " 秒")nendfunctionn

重點是

python << EOFn# python 代碼在這裡nEOFn

這個結構限定了 python 代碼的區域,在這個範圍內寫 python 才有效果。另外必須注意縮進,這個參考上面的代碼就能知道了。

EOF 標記是可以改的,沒有限定,但必須注意 EOF 不能縮進:

python << FUCKn# python codenFUCKn

3.3 vim 模塊

如果我要用 python ,為什麼不直接寫個 py 腳本呢?因為 vim 為我們提供了一個 vim 模塊,讓我們可以直接用 python 和 vim 進行交互,能夠同時結合 VimL 的簡單和 python 的功能。

在開始之前,我們先隨便試試。在我們的測試文檔中

:python b=vim.current.buffer "獲取整個 buffer,賦值給變數 b,這個 b 是n "python 的變數,vim 命令不能調用。nn:py print b[1] "python 可以簡寫為 py。這個命令用於顯示 buffern "的第 2 行。這個命令是從 0 開始計數的。nn:py b[1]="fuck" "把第 2 行的內容變成 fuckn

好,你現在應該大概知道是怎麼回事了,我們來試試上面腳本。

同樣是寫得很粗糙的函數,但是效率卻是 VimL 的 9 倍!因為 vim 操作會有些效能上的損失,所以單純的 python 腳本效率還會更高點,有興趣的可以自己測試。

4. perl

perl 是很適合用來做文本處理的語言,有著功能強大的正則表達式,當 vim 的正則表達式無法滿足需求的時候,用 perl 是個不錯的選擇。

不過今天我們暫時不涉及這方面的內容,先來看看怎麼在 vim 腳本中用 perl 吧。

和 python 一樣,vim 也內置了一篇文檔:

:help perln

看看你的 vim 是不是能用 perl:

:echo has("perl")n:perl print "Hello Vim"n

不行的話參照 python 的情況來解決即可。

隨便試試:

:pe print $curbuf->Get(2) "perl 可以簡寫為 pe 。這個命令用於顯示 buffern "的第 2 行。這個命令是從 1 開始計數的。nn:pe $curbuf->Set(2, "fuck") "把第 2 行的內容變成 fuckn

接著,我們來看看用 perl 寫的粗糙函數:

function! RenameByPerlV1()n let start_time = localtime()n echo("開始處理...")nperl << EOFn $lnum = 1;n $num = 0;n $c = 0;n while ($lnum <= $curbuf->Count()) {n if ($curbuf->Get($lnum) eq "value") {n $num = 0;n }n elsif ($curbuf->Get($lnum) eq "wave") {n $curbuf->Set($lnum, "wave . $num . ");n $c += 1;n $num += 1;n }n $lnum += 1n }n VIM::DoCommand("let c=" . $c);nEOFn redraw!n echo("完成!n共替換 " . c ." 行n耗時:" . (localtime()-start_time) . " 秒")nendfunctionn

標記方式和 python 一樣,不過 perl 代碼沒有縮進的限制(但 EOF 同樣不能縮進)。

perl << EOFn # perl code herenEOFn

現在,我們就來看看效果如何:

效率是 VimL 的 3 倍。

5. 結語

對比較大文本的處理,python 和 perl 顯然都是更好的選擇。在某些需求中,我們會希望逗留在 vim 界面,同時又希望有更高效的執行效率。比如在處理某些不那麼規則的文本,我們要先進行一些預處理,再進行不規則的處理,此時通過外部腳本直接對 vim buffer 進行操作是個不錯的主意。

但是,我再次提醒,如果是純批量處理的需求,而 vim 顯然無法承擔的時候,讓 vim 靜靜當一個「閱讀器」,把處理的工作交給適合的工具吧。


推薦閱讀:

【小技巧】對文本中指定內容進行快速的全局刪改
vim 怎麼脫離滑鼠?
通過列表添加文件到「參數列表」(argslist)
SpaceVim - 讓你的vim變得更加高效和強大
vim短途跳轉用easymotion那長途呢?

TAG:Vim | vim脚本 | 文本编辑 |