VIMの文字列のリストから置換するにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/1809571

  •  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^[のようになります。マクロモードに慣れていない場合は、単にqa(マクロを保存するレジスタ)、次にキーストローク/foo <Enter> cwbar <Escape>を押します。

マクロを取得したら、2@aを実行して現在のバッファー内の最初の2つのオカレンスを置換し、通常の:sを使用して残りを置換します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top