Wie ersetzt ich eine Liste von Zeichenfolgen in VIM?
-
05-07-2019 - |
Frage
Ich bin ein VIM -Benutzer und möchte in der Lage sein, über eine Reihe von Substrings zu gehen, wenn ich ersetzt. Wie kann ich eine VIM -Magie verwenden, um von einer Reihe von Zeilen wie diesem zu entstehen:
Afoo
Bfoo
Cfoo
Dfoo
zu
Abar
Bbar
Cbaz
Dbaz
?
Ich möchte meine Datei vom Start nach dem nächsten Ereignis von durchsuchen foo
, und ersetzen Sie die ersten beiden Fälle durch bar
, und die zweiten zwei mit baz
. Ist die Verwendung von A for Loop die beste Option? Wenn ja, wie verwende ich dann die Schleifenvariable im Substitutionsbefehl?
Lösung
Ich würde eine Funktion verwenden, die einen Zustand hat, und diese Funktion aus %s aufrufen. Etwas wie:
" 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
Und benutze sie mit:
:call InitRotateSubst()
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/
Der Anruf zu den beiden Befehlen könnte in einem einzigen Befehl eingekapselt werden, wenn Sie dies wünschen.
Bearbeiten: Hier ist eine Version, die als Befehl integriert ist, dass:
- Akzeptiert so viele Ersetzungen, wie wir es wünschen, alle Ersetzungen müssen mit dem Trennzeichen getrennt werden.
- unterstützt Rückenreferenzen;
- Kann nur die ersten Vorkommen ersetzen, n == die Anzahl der angegebenen Ersetzungen, wenn der Befehlsaufruf geschlagen wird (mit einem!)
- unterstützt keine üblichen Flaggen wie G, ich (ich (
:h :s_flags
)-dafür hätten wir zum Beispiel, dass der Befehlsaufruf immer mit A / (oder allen Trennzeichen) den Befehlsaufruf auferlegt, wenn nicht der letzte Text als Flags interpretiert wird.
Hier ist die Befehlsdefinition:
: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
das könnte auf diese Weise verwendet werden:
:%RotateSubstitute#foo#bar#bar#baz#baz#
oder sogar unter Berücksichtigung des ersten Textes:
AfooZ
BfooE
CfooR
DfooT
der Befehl
%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/
würde produzieren:
ZbarA
BbarE
RbarC
DbarT
Andere Tipps
Dies ist nicht genau das, was Sie wollen, kann aber für Zyklen nützlich sein.
Ich habe einen Plugin -Swapit geschrieben http://www.vim.org/scripts/script.php?script_id=2294 Was unter anderem beim Radfahren durch Stringslisten helfen kann. Z.B.
:Swaplist foobar foo bar baz
Geben Sie dann ein
This line is a foo
Erstellen Sie eine einfache Yank-/Paste-Linie, gehen Sie zu Last Word und Strg-A Swap.
qqyyp$^A
Führen Sie dann das Tauschmuster aus
100@q
bekommen
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
...
Es könnte wahrscheinlich auf Ihr Problem angewendet werden, obwohl es {cWord} sensitiv ist.
Warum nicht:
:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/
Ich bin mir nicht sicher, ob es die Breite des Problems abdeckt, hat aber die Tugend, ein einfacher Ersatz zu sein. Ein komplexerer kann die Lösung abdecken, wenn diese nicht der Fall ist.
So würde ich dieses Makro versuchen.
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.
Jeder Rat, wie man es besser macht, ist willkommen.
Danke, Martin.
Es wird wahrscheinlich viel einfacher sein, ein Makro aufzunehmen, das die ersten beiden ersetzen kann, und dann: s für den Rest verwenden.
Das Makro könnte so aussehen /foo^Mcwbar^[
. Wenn Sie mit dem Makromodus nicht vertraut sind, klicken Sie einfach q
, a
(Das Register, um es zu speichern) und dann die Tastenanschläge /foo <Enter> cwbar <Escape>
.
Jetzt, sobald Sie das Makro haben, tun Sie es 2@a
Um die ersten beiden Vorkommen im aktuellen Puffer zu ersetzen und zu verwenden :s
Normalerweise ersetzen Sie den Rest.