VIMの文字列のリストから置換するにはどうすればよいですか?
-
05-07-2019 - |
質問
私はvimユーザーであり、置換するときに部分文字列の範囲をループできるようにしたいと考えています。 vimマジックを使用して、次のような一連の行から移動するにはどうすればよいですか:
Afoo
Bfoo
Cfoo
Dfoo
to
Abar
Bbar
Cbaz
Dbaz
?
次のfoo
の出現をファイルの最初から検索し、最初の2つのインスタンスをbar
で、次の2つのインスタンスをbaz
で置き換えたい。 forループを使用するのが最適なオプションですか?その場合、置換コマンドでループ変数を使用するにはどうすればよいですか?
解決
状態を持つ関数を使用し、この関数を%sから呼び出します。次のようなもの:
" untested code
function! InitRotateSubst()
let s:rs_idx = 0
endfunction
function! RotateSubst(list)
let res = a:list[s:rs_idx]
let s:rs_idx += 1
if s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
return res
endfunction
そして以下で使用します:
:call InitRotateSubst()
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/
必要に応じて、2つのコマンドの呼び出しを1つのコマンドにカプセル化できます。
編集:これはコマンドとして統合されたバージョンです:
- 希望する数の置換を受け入れます。すべての置換は区切り文字で区切る必要があります。
- 後方参照をサポートします。
- 最初のN個のみを置換できます。N==コマンド呼び出しが強打された場合に指定された置換の数(!を使用)
- は、g、i(
:h :s_flags
)などの通常のフラグをサポートしていません-そのため、たとえば/最後のテキストはフラグとして解釈されます。
コマンド定義は次のとおりです。
:command! -bang -nargs=1 -range RotateSubstitute <line1>,<line2>call s:RotateSubstitute("<bang>", <f-args>)
function! s:RotateSubstitute(bang, repl_arg) range
let do_loop = a:bang != "!"
" echom "do_loop=".do_loop." -> ".a:bang
" reset internal state
let s:rs_idx = 0
" obtain the separator character
let sep = a:repl_arg[0]
" obtain all fields in the initial command
let fields = split(a:repl_arg, sep)
" prepare all the backreferences
let replacements = fields[1:]
let max_back_ref = 0
for r in replacements
let s = substitute(r, '.\{-}\(\\\d\+\)', '\1', 'g')
" echo "s->".s
let ls = split(s, '\\')
for d in ls
let br = matchstr(d, '\d\+')
" echo '##'.(br+0).'##'.type(0) ." ~~ " . type(br+0)
if !empty(br) && (0+br) > max_back_ref
let max_back_ref = br
endif
endfor
endfor
" echo "max back-ref=".max_back_ref
let sm = ''
for i in range(0, max_back_ref)
let sm .= ','. 'submatch('.i.')'
" call add(sm,)
endfor
" build the action to execute
let action = '\=s:DoRotateSubst('.do_loop.',' . string(replacements) . sm .')'
" prepare the :substitute command
let args = [fields[0], action ]
let cmd = a:firstline . ',' . a:lastline . 's' . sep . join(args, sep)
" echom cmd
" and run it
exe cmd
endfunction
function! s:DoRotateSubst(do_loop, list, replaced, ...)
" echom string(a:000)
if ! a:do_loop && s:rs_idx == len(a:list)
return a:replaced
else
let res0 = a:list[s:rs_idx]
let s:rs_idx += 1
if a:do_loop && s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
let res = ''
while strlen(res0)
let ml = matchlist(res0, '\(.\{-}\)\(\\\d\+\)\(.*\)')
let res .= ml[1]
let ref = eval(substitute(ml[2], '\\\(\d\+\)', 'a:\1', ''))
let res .= ref
let res0 = ml[3]
endwhile
return res
endif
endfunction
この方法で使用できます:
:%RotateSubstitute#foo#bar#bar#baz#baz#
または、最初のテキストを考慮して:
AfooZ
BfooE
CfooR
DfooT
コマンド
%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/
生成されるもの:
ZbarA
BbarE
RbarC
DbarT
他のヒント
これは厳密にはあなたが望むものではありませんが、サイクルに役立ちます。
プラグインswapit http://www.vimを作成しました。 org / scripts / script.php?script_id = 2294 を使用すると、文字列のリストを順に切り替えることができます。例:
:Swaplist foobar foo bar baz
次に入力
This line is a foo
単純なヤンク/ペースト行を作成し、最後の単語に移動してctrl-aスワップします。
qqyyp$^A
次にスワップパターンを実行します
100@q
取得するには
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
...
{cword}に敏感ですが、問題に適用される可能性があります。
理由:
:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/
それが問題の幅をカバーするかどうかはわかりませんが、単純な代替物であるという長所があります。これが解決しない場合、より複雑なものがソリューションをカバーするかもしれません。
これは、私がそのマクロを試みる方法です。
qa Records macro in buffer a
/foo<CR> Search for the next instance of 'foo'
3s Change the next three characters
bar To the word bar
<Esc> Back to command mode.
n Get the next instance of foo
. Repeat last command
n Get the next instance of foo
3s Change next three letters
baz To the word bar
<Esc> Back to command mode.
. Repeat last command
q Stop recording.
1000@a Do a many times.
より良い方法についてのアドバイスは大歓迎です。
ありがとう、 マーティン。
おそらく、最初の2つを置き換えることができるマクロを記録し、残りに:sを使用する方がはるかに簡単になるでしょう。
マクロは/foo^Mcwbar^[
のようになります。マクロモードに慣れていない場合は、単にq
、a
(マクロを保存するレジスタ)、次にキーストローク/foo <Enter> cwbar <Escape>
を押します。
マクロを取得したら、2@a
を実行して現在のバッファー内の最初の2つのオカレンスを置換し、通常の:s
を使用して残りを置換します。