2010-08-26 22 views
34

Was ist der einfachste Weg, um alle Vorkommen von string_a durch string_b zu ersetzen und gleichzeitig alles, was bereits string_b war, in string_a zu ändern? Meine aktuelle Methode ist wie folgt:Einfachste Möglichkeit, Vorkommen von zwei Strings in Vim zu tauschen?

:s/string_a/string_c/g 
:s/string_b/string_a/g 
:s/string_c/string_b/g 

Obwohl dies funktioniert, erfordert es zusätzliche Typisierung und scheint ineffizient. Weiß jemand einen besseren Weg, dies zu tun?

+2

Sie könnten diese Frage auch auf SuperUser.com ausprobieren. – janmoesen

+0

Das ist eine gute Frage - man würde das einfach denken ... Ich könnte mir vorstellen, dass man eine Funktion schreiben könnte, die zwei Parameter akzeptiert und die drei Schritte für Sie durchläuft, aber ich hätte auch eine solche Funktion mit einer schnellen Websuche erwartet . – Jay

+1

Dieser Ansatz würde fehlschlagen, wenn Ihre Datei irgendwo schon "string_c" enthalten würde. Es ist in Ordnung, wenn Sie ein Mensch sind und ein Wort auswählen können, von dem Sie wissen, dass es nicht in Ihrer Datei ist, aber es wäre schwieriger, eine Funktion zu lehren, ein gutes Zwischenwort zu erraten. Besser, es in einem Durchgang zu tun. –

Antwort

25

ich es so tun würde:

:%s/\v(foo|bar)/\={'foo':'bar','bar':'foo'}[submatch(0)]/g 

Aber das ist zu viel schreiben, würde ich dies so tun:

function! Mirror(dict) 
    for [key, value] in items(a:dict) 
     let a:dict[value] = key 
    endfor 
    return a:dict 
endfunction 

function! S(number) 
    return submatch(a:number) 
endfunction 

:%s/\v(foo|bar)/\=Mirror({'foo':'bar'})[S(0)]/g 

Aber das erfordert noch zweimal foo und bar eingeben, so dass ich so etwas tun würde:

function! SwapWords(dict, ...) 
    let words = keys(a:dict) + values(a:dict) 
    let words = map(words, 'escape(v:val, "|")') 
    if(a:0 == 1) 
     let delimiter = a:1 
    else 
     let delimiter = '/' 
    endif 
    let pattern = '\v(' . join(words, '|') . ')' 
    exe '%s' . delimiter . pattern . delimiter 
     \ . '\=' . string(Mirror(a:dict)) . '[S(0)]' 
     \ . delimiter . 'g' 
endfunction 

:call SwapWords({'foo':'bar'}) 

Wenn einer Ihrer Worte ein / enthält, müssen Sie in einem Begrenzer passieren die Sie weiß keiner Ihrer Worte enthält, .EG

:call SwapWords({'foo/bar':'foo/baz'}, '@') 

Dies hat auch den Vorteil, in der Lage, mehrere Paare von Wörtern auf einmal zu tauschen.

:call SwapWords({'foo':'bar', 'baz':'quux'}) 
+0

Das ist großartig! Mein einziges Problem ist, dass es die Vorkommen überall tauscht. Gibt es eine Möglichkeit, die Zeilen zu übergeben, auf denen der Austausch stattfinden soll, ähnlich wie ': 17,34s/foo/bar/g' nur die Zeilen 17 bis 34 ersetzt? – dsg

+1

@dsg Kann gemacht werden, indem zwei Änderungen an SwapWords() vorgenommen werden. Setze das Schlüsselwort 'range' nach dem' ...) 'in die erste Zeile und ersetze' '% s'' am Ende mit' a: firstline. ",". a: Letzte Zeile. 's''. Dann wird es einen Bereich genau wie ': s' nehmen. – 00dani

1

Sie können es mit einem einzigen Befehl tun, wie unten in meinem Code gezeigt:

:%s/\<\(string_a\|string_b\)\>/\=strpart("string_bstring_a", 8 * ("string_b" == submatch(0)), 8)/g 

Update: der Herausgeber hier in Stackoverflow ist mich verrückt, weil es das Entfernen backslahes hält ...

8

Sie können dies leicht mit Tim Popes Abolish Plugin

:%S/{transmit,receive}/{receive,transmit} 
+0

% S/{, }/{, }/g Wenn ich versuche, ganze Wörter zu tauschen, funktioniert es nicht. Was soll ich machen? –

+1

@JanNetherdrake Wenn Sie ganze Wörter verwenden möchten, verwenden Sie Subverts 'w' -Flag. ':% S/{test, foo}/{foo, test}/gw'. Weitere Informationen finden Sie unter ': h dispolish-search'. –

2

Die swapstrings plugin einen handlichen Befehl hierfür lauten:

:SwapStrings string_a string_b 
2

Hier ist, wie ich tausche zwei Worte skip & limit .

%s/skip/xxxxx/g|%s/limit/skip/g|%s/xxxxx/limit/g

Ziemlich sicher, dass jemand sie in einen kürzeren Befehl drehen könnte, die zwei Argumente akzeptiert.

Verwandte Themen