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

其實是兩個需求,例子我用圖片的形式。

一是使wave後面添加數字並且能從0開始遞增:二是出了花括弧之後又重新從0開始數字遞增。


.

用 vim 打開你的文檔,確保處於 normal 模式,然後執行以下操作

qa
:let i=0
vi{
:g/"wave"/s//=""wave" . i . """/|let i=i+1
q
/"value"
:%norm j@an

完成。

說明見下面:

根據我的理解,需求是有 N 組「 value 」,每組「 value 」下面有 M 個「 wave 」,為「 wave 」進行編號,每組「 wave 」編號獨立。

相當於這樣的結構:

value
wave1
wave2
wave3
wave4
value
wave1
wave2
wave3
value
wave1
value
wave1
wave2

如果從編程的角度考慮,就是設置兩個變數,value 一個,wave 一個。在每個 value 下的 wave 進行編號,到下一個 value 時,wave 的編號歸零重新開始。

但是這樣你得寫個函數來實現,而寫函數,就意味著你要建立一個腳本來保存並執行它。

對於目前這種簡單的一次性需求(下一次就不是替換這些內容了),還要新建個文件實在是太小題大作。

實際上從 vim 操作的角度,可以這樣考慮:

先搜索 value,每找到一個 value,執行一次 wave 編號就 ok 了。所以是兩套操作:找到 value 並執行編號操作;進行編號的操作。

我們先來處理進行編號的操作。錄製一個宏,以便後面調用

qa "錄製宏到寄存器 a
:let i=0 "初始化編號為 0,存在變數 i 中
vi{ "選擇 {} 中的全部內容,vim 自帶操作
:g/"wave"/s//=""wave" . i . """/|let i=i+1 "添加編號
q "完成錄製

關於編號,看這裡的第一節第 2 點:知乎專欄

如果你怕 global 命令太長,錄宏的時候打錯,那麼可以直接把 global 命令存到一個寄存器裡面,當宏來執行。但是為了避免打錯,我們不錄製宏,而是直接為寄存器賦值:

:let @g="g/"wave"/s//\=""wave" . i . """/|let i=i+1
"

" 在賦值的時候命令要用 " 包圍,因此命令本身的 " 就要替使用 轉義,寫成 " ,反斜杠 也
" 是一樣,要寫成 \ 。
" 另一方面,這個命令在你調用的時候是不會直接執行的,而是直接出現在命令行中,需要你按回車
" 才會執行。如果不想那麼麻煩,就在命令的背後加個回車(

,^M )都可以。不過要注意
" 這裡的 ^M 是特殊符號,不是 ^ 和 M 的組合,要通過 ctrl+q ctrl+m 來輸入。

然後你錄製宏的時候這樣操作:

qa
:let i=0
vi{
@g
q

最後是 value 的部分:

/"value" "搜索 "value"
:%norm j@an "全文檔執行 normal 模式操作。
"先 j 下一行,到 { 。
"執行宏 @a ,這樣選中的部分就會編號。
" n 跳到下一個搜索結果(即下一個 "value")
"最後會報錯,因為最後一次執行 normal 命令會找不到 "wave" 了,不用管,說明已
"經全部替換完了。

關於 normal 命令的使用,參照這裡的第二節:知乎專欄

注意,因為 vim 不允許用 global 命令調用 global 命令,所以這樣的命令

g/"value"/norm j@a

是不行的。

此外,如果文本比較大,那麼宏就無力處理(會非常慢,時間以數十分鐘計)。請用 awk 之類的工具或者通用腳本語言來處理。用 vim language 也能處理,就是有點慢(人家 1 秒不到處理完的,可能 VimL 要幾秒)。

說明見這篇專欄:萬行以上文本的分組編號——在 VIM 中嵌入 python 和 perl 腳本

VimL 方案:

function! RenameByVim()
let lnum = 1
let num = 0
while lnum &<= line("$") let con = getline(lnum) if con == " "value"" let num = 0 elseif con == " "wave"" call setline(lnum, " "wave"" . num . """) let num += 1 endif let lnum += 1 endwhile endfunction

python 方案:

function! RenameByPython()
python &<&< EOF import vim vimBuffer = vim.current.buffer num = 0 l = 0 while l &< len(vimBuffer): if vimBuffer[l] == " "value"": num = 0 elif vimBuffer[l] == " "wave"": vimBuffer[l] = " "wave" + str(num) + """ num += 1 l += 1 EOF endfunction

perl 方案:

function! RenameByPerlV1()
perl &<&< EOF $lnum = 1; $num = 0; $c = 0; while ($lnum &<= $curbuf-&>Count()) {
if ($curbuf-&>Get($lnum) eq " "value"") {
$num = 0;
}
elsif ($curbuf-&>Get($lnum) eq " "wave"") {
$curbuf-&>Set($lnum, " "wave" . $num . """);
$c += 1;
$num += 1;
}
$lnum += 1
}
VIM::DoCommand("let c=" . $c);
EOF
redraw!
echo("完成!
共替換 " . c ." 行
耗時:" . (localtime()-start_time) . " 秒")
endfunction


windows嘛~啊哈哈,windows批處理是萬能的,我來寫個等價於上面vim腳本的方法吧(需求雖然和我分析的不一樣,比如我分析的需求沒有強制他裡面只能是wave,不過下面的批處理還是按照等價於上面vim腳本的方式來寫的,即裡面一定是wave)

setlocal enableextensions enabledelayedexpansion
goto start
:replace
@echo !line0:wave=wave%1!
@goto:eof

:start
rem 下面兩行可以改成interactive形式,也可以改成直接使用命令行參數,沒見過世面的"linux高手"勿噴
set input="source.txt"
set output="out.txt"
set /p flag=""&out.txt
set flag=0
set count=0
for /f "usebackq delims=" %%a in (%input%) do (
set line0=%%a
set line=!line0: =!
set line=!line: =!
if !flag!==0 (
if "value"1==!line!1 set flag=1
echo %%a
) else (
if "wave"1==!line!1 (
call:replace !count!
set /a count=count+1
) else (
echo %%a
if not "{"=="!line!" (
set flag=0
set count=0
)
)
)
) &>&> %output%

p.s. windows上要處理的話,樓主這種format好的表現是可以很簡單完成的。如果是unformatted不是不可以而是很難。

(不過文中因為values之間除了換行並沒有別的分隔符,所以應該可以默認是format好的)

(unformatted比如我可以寫個批處理來format像json這樣的換行無意義的東西,當然我沒寫過,因為效率不會高,如果有人給我1000塊我可以考慮寫,不然噴子勿噴。)

隨手丟個小技巧 set /p unused_var="text without EOL" &out.txt 可以在out.txt里列印出不代換行的那行字,這個技巧可以廣泛用於format/parse任何換行不敏感的語法中(如json)

p.s.2 如果需求里的wave換成隨機單詞,單詞後加入的數字該單詞之前出現的次數,例如

"wave" -&> "wave0"

"sea" -&> "sea0"

"wave" -&> "wave1"

反正windows批處理大法在隨機單詞不含特殊字元的情況下可以(理論上含特殊字元的藉助哈希和臨時文件也可以解決,不過就是寫起來太累了)

p.s.3 這篇回答是用來打那些學習了1000個小時Linux/vim的使用但是卻從來沒有在學習Windows使用上花1個小時的,然後到處說Windows什麼都幹不了的"linux高手"的臉

真正的高手應該是像我之前在知乎看到的一片回答里說的那樣(現在我一時找不到那篇回答了),會說這個東西我知道在Linux/Windows上怎麼弄我可以幫你弄,但Windows/Linux上應該也可以弄只是我不知道

比如這個題我在windows上用for循環解決,在Linux上應該是可以用awk來解決的,只不過我菜,不會弄,我覺得就這個需求而言用vim並不比windows的for/Linux的awk優雅,甚至需求擴展有局限性(比如上面的p.s.2中提出的需求擴展)

EOF


提幾個點

1,用vim的宏記錄

2,vim normal模式下的ctrl-a是遞增數字,ctrl-x是遞減數字


用VisIncr這個插件:http://vim.sourceforge.net/scripts/script.php?script_id=670


推薦閱讀:

如何避免無意識裝B?
win8環境下python3.4怎麼樣配置才能把scrapy安裝成功?
Python中,if與elif有何區別?
Abaqus中如何根據不同的材料來創建對應的set?

TAG:Python | 編程 | Linux | Vim | Java正則 |