7

Let's say that I have a file containing several occurrences of the word foo and several occurrences of the word bar.

I need to make all the words foo become bar and inversely all the words bar become foo.

What is the most efficient way to do that?


My first thought was to make it 3 steps:

  • First make bar become something temporary: %s/bar/barTEMP/g
  • Then make foo become bar: :%s/foo/bar/g
  • Finally substitute barTEMP: :%s/barTEMP/foo/g

It is an implementation of a classical algorithm to switch the value of two variables but is there a more efficient way to do this?


I know I can write a function to do the 3 substitutions for me but what I'm wondering is if there is a different method than using a temporary substitution. For example I was thinking maybe something could be done by using an | in the search pattern:

:%s/\(foo\|bar\)/???/g

But that would require to make the replacement string change depending on the actually matched word and I don't know how to do that. (Note that this idea is just an example, if someone come up with something better that would be nice too)

statox
  • 49,782
  • 19
  • 148
  • 225
  • 1
    http://stackoverflow.com/a/9273857/1924583 – Tumbler41 Aug 05 '16 at 13:35
  • 3
    I suppose this is the original source with full commentary on how it works. – Tumbler41 Aug 05 '16 at 13:40
  • Tim Pope's abolish provides some syntactic sugar for it: :%S/{foo,bar}/{bar,foo}/g. This works well as long as you don't want to match only whole words. – Sato Katsura Aug 05 '16 at 13:42
  • 1
    I usually use the ternary operator in a replacement expression for just two words (not very obviously at :h expr1), though using a dictionary is obviously better for >2 words. – Antony Aug 05 '16 at 14:11
  • @SatoKatsura: Thanks! I had seen this plugin before but totally forgot it could do that. Currently I'm trying to reduce the number of plugins I use so that's not the solution I'm going to use but that is still good to know! – statox Aug 05 '16 at 15:15
  • @Antony: I guess for a 2 words substitution like in my question, the ternary operator is the best way to go because it is lighter than a dictionary. (And thanks for the reminder about :h expr1 I always forget how to find it! – statox Aug 05 '16 at 15:18
  • 1
    Yeah, it took me several minutes to find it, so, when I finally did, I thought I'd better mention where it was. :-) – Antony Aug 05 '16 at 16:30
  • Just adding in the actual command for using the ternary operator as @Antony suggested: :%s/foo\|bar/\=submatch(0) == "foo" ? "bar" : "foo"/g in case non-programmers want to use it. – Rich Oct 27 '17 at 09:13

2 Answers2

15

Posting this so it can have an answer on our SE, but this is this source with full explanation.

Here's a good way to use a single regex to do multiple substitutions:

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

See :help sub-replace-expression and :help Dictionary for Vim's documentation on how this works.

Tumbler41
  • 7,746
  • 1
  • 21
  • 48
  • Thanks for this one, I didn't saw it on SO. That seems like a good way to go. – statox Aug 05 '16 at 15:13
  • 1
    Just mentioning the solution using the ternary operator that @Antony suggested above: :%s/foo\|bar/\=submatch(0) == "foo" ? "bar" : "foo"/g. – Rich Oct 27 '17 at 09:15
2

For vanilla vim, Tumbler41's answer is probably the best way to go. If you are OK with installing plugins, you could use tpope/vim-abolish to do this.

This plugin supplies many awesome feature, but the relevant one for this case is called "Subvert" (abbreviated to :%S. Essentially, it allows you to create a list of matches, and a list of replacements. I like the example from the README on the plugin, so I'll use that. If you want to change "child" to "adult", while simultaneously changing "children" to "adults", that is a single subvert command.

:%S/child{,ren}/adult{,s}/

In your case, you could do

:%S/{foo,bar}/{bar,foo}

One important note about this approach is that it doesn't support regexes very well, and it defaults to case-sensitive, so it would also change "FOO" to "BAR". However, for simple substitutions, this approach is very simple and convenient.

DJMcMayhem
  • 17,581
  • 5
  • 53
  • 85
  • abolish does support regexes, it's just that (1) it uses "very magic" mode, and (2) your regexes are likely to conflict with its own. Like the vast majority of Vim plugins, it's an useful hack, not a rock-solid solution. – Sato Katsura Aug 06 '16 at 04:20
  • You mean “it defaults to case-insensitive,” right? – Soren Bjornstad Aug 07 '16 at 22:22
  • Vimcast episode: [Supercharged substitution with :Subvert]http://vimcasts.org/episodes/supercharged-substitution-with-subvert/) – Peter Rincker Aug 10 '16 at 18:01