What is the easiest way to swap occurrences of two strings in Vim?
Asked Answered
A

6

46

What is the easiest way to replace all occurrences of string_a with string_b while at the same time changing anything that was already string_b into string_a? My current method is as follows:

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

Although this works, it requires extra typing and seems inefficient. Does anybody know of a better way to do this?

Assent answered 26/8, 2010 at 19:1 Comment(5)
You might (also) want to try this question on SuperUser.com.Chromyl
That's a great question -- one would think this easy… I imagine one could write a function that accepts two parameters and goes through the three steps for you, but I'd also have expected to find such a function with a quick web search.Priscella
This approach would fail if your file contained "string_c" anywhere already. It's fine if you're a human and can pick a word that you know isn't in your file, but it'd be more difficult to teach a function to guess a good intermediary word. Better to do it in one pass.Fossil
@BrianCarper I think it can fail even if string_c doesn't appear in the text, e.g. if the text is the alphabet "abcde...", string_a is "bcd" and string_c is "aa".Ironbound
As Peter Rincker answered on another question, the Abolish plugin now does this nicely too!Galenical
F
27

I'd do it like this:

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

But that's too much typing, so I'd do this:

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

But that still requires typing foo and bar twice, so I'd do something like this:

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'})

If one of your words contains a /, you have to pass in a delimiter which you know none of your words contains, .e.g

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

This also has the benefit of being able to swap multiple pairs of words at once.

:call SwapWords({'foo':'bar', 'baz':'quux'})
Fossil answered 26/8, 2010 at 20:12 Comment(2)
This is great! My only issue is that it swaps the occurrences everywhere. Is there a way to pass the lines on which you want the swap to occur similar to how :17,34s/foo/bar/g will only substitute on lines 17 to 34?Abseil
@Abseil Can be done by making two changes to SwapWords(). Put the keyword range after the ...) on the first line, then replace '%s' near the end with a:firstline . ',' . a:lastline . 's'. Then it'll take a range exactly like :s does.Siddra
E
17

You can do this easily with Tim Pope's Abolish plugin

:%S/{transmit,receive}/{receive,transmit}
Electro answered 10/7, 2013 at 13:28 Comment(2)
%S /{<Test>,<Foo>}/{<Foo>,<Test>}/g When I try to swap whole words, it doesn't work. What should I do?Berman
@JanNetherdrake If you want to use whole words then use Subvert's w flag. :%S/{test,foo}/{foo,test}/gw. See :h abolish-search for more information.Electro
I
6

Here is how I swap two words skip & limit:

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

Pretty sure someone could turn it into a shorter command which accepts two arguments.

Indusium answered 19/6, 2016 at 5:10 Comment(0)
B
3

The swapstrings plugin provides a handy command for this:

:SwapStrings string_a string_b
Bertiebertila answered 23/12, 2013 at 20:23 Comment(0)
T
1

You can do it with a single command as shown in my code below:

:%s/\<\(string_a\|string_b\)\>/\=strpart("string_bstring_a", 8 * ("string_b" == submatch(0)), 8)/g
Tollbooth answered 26/8, 2010 at 19:16 Comment(0)
E
0

Here is an example how to swap 2 strings separated by space bar within one line. One can use:

:'a,'bs/\(.*\s\)\(.*$\)/ \2 \1/g

where 'a,'b defines section where this command is applied
      .*\s specifies string from begin of the line until space bar (\s)
      .*$  specifies the section until the end of the line
      \2 \1  in substitution use first section 2 and then section 1
Entablement answered 22/1 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.