Vim Substitute Tricks

Posted on
VimExplorer vim vimscript tricks
Vim Substitute Tricks /img/vim-substitute-pic.jpg

I use Vim to edit text every day, and the good old :[range]s[ubstitute]/pattern/string/flags command is without doubt one of the commands I use the most. It is simple yet powerful, and when combined with the g flag allows me to replace everything I want on an arbitrary selection (or on the whole file using :%s/pattern/replacement/g).

But I recently realized that, despite using it a lot, I was far from leveraging its full potential. And I am pretty sure I’m not the only one! In this article, I’ll introduce various little :substitute tricks that I’m currently trying to integrate into my daily workflow.

There Is More Than the g Flag!

Added at the end of the :substitute command (after the last /), flags modify the behavior of the command in various ways. For example, without the g flag, :s/pattern/repl/ will only replace the first occurrence and stop there! But because :s/pattern/repl/g does what we need most of the time (i.e. replace all occurrences of pattern with repl), we tend to forget about the existence of other flags. A quick :help s_flags shows that many more exist:

  • &
  • r
  • g
  • c
  • n
  • e
  • i
  • I
  • p
  • #
  • l

The ambition of this article is not to rewrite the Vim help, so I will only talk about the flags I use the most, but I highly encourage you to give the help a read and learn about the rest.

The & and r Flags

& and r are interesting in their own right, and I’ll talk about them a little more in the repeating substitutions section.

The g Flag

I’ve said g is really common and well known, but a common misconception is that without it, :substitute will only replace the first match. However, a simple :[range]s/pattern/repl/ will replace only the first occurrence of patternfor each line in range! This means that if your file looks like:

I like dogs

But dogs are better

And you run :%s/dogs/cats/ then both occurrences of dogs will be replaced! Be careful :)

The c Flag

c is probably the other really well known flag (at least, it’s the only other one I knew!). Instead of replacing silently like before, adding c will ask you for confirmation before replacing anything. This can be useful when you’re not sure about brute force replacing, and want to control exactly what Vim does. It also improves the granularity of the command, as each replacement become an individual operation that can be undone (with u) on its own (as opposed to undoing all replacements in the regular case).

When using c, Vim will prompt you for a character before each replacement. The most common are y for yes (replace it) and n for no (don’t replace it and go to next match). There are however a bunch of other options, among which a few can be really useful:

  • a for all to replace the current match and all remaining matches (nice to have when you realize midway that you would like to replace everything without being prompted!),
  • l for last to substitute this match and stop (when you realize you don’t want to replace anything else), and
  • q for quit to stop (like l but does not replace the current match).

The n Flag

This is an odd one, as it will not perform any substitution, but instead report the number of matches. For instance, if you want to know the number of times pattern is in your file, you can run :%s/pattern//ng (be careful though… without the n flag, this will erase all occurrences of pattern!).

Note that all flags can be combined together, and n is no exception. So, given:

foo bar foo

baz foo

:%s/foo//n will print 2 matches on 2 lines (only counting the first match on each line), but :%s/foo//gn will print the expected 3 matches on 2 lines. And while the combination with g is certainly the most useful, n can be combined with all flags but c.

Some other useful examples include:

  • :%s/\i\+//gn to count all words in a file, or
  • :%s/^//gn to count all lines in a file

See :help count-items for more!

Side note: using n (or any :substitute command for that matter) will cause the search pattern to stay highlighted even after the substitute command finishes (if you don’t think it’s annoying, try :%s/.//gn to count all characters with :set hlsearch on… you won’t be disappointed). Unfortunately, the only way to remove it is to manually type :noh.

Repeating Substitutions

Another common scenario is repeating substitutions. If you want to redo the exact same command, you can of course manually go up the list of commands you typed using the arrow keys in command-line mode, but what if you want to repeat only part of it? Once again, Vim has you covered.

Before we start this section, two important things:

  • Repetition (whether of the entire :substitute command or of just a part of it) works across buffers, so you can easily repeat commands across multiple buffers!
  • The range does not get repeated! So in any case, don’t forget to add it in front of the command.

Repeating With Different Flags

You can repeat the exact same substitute command with :s or :&, with a twist: it resets the flags. What can this be used for, you ask? Well, here is something I sometimes found myself doing lately: counting the number of matches before replacing all of them. Of course, you can do something like:

:%s/pattern//gn
:%s/pattern/repl/g

But I known you want to be a cool Vimmer, and cool Vimmers can instead do:

:%s/pattern/repl/gn
:%&g

Which can save you quite a few strokes if pattern is long!

Note that I used :& instead of :s: in this case, both work, but :s in its shortened form does not support the & flag, so I advise you to remember :& instead as it does not suffer from this limitation.

Repeating Flags

That’s what the & flag is for. It has to be the first flag used in a command, and you can combine it with new flags if you so wish.

I’m sure there are uses for it, but I quite honestly cannot think of any aside of :&& which means “run the same substitute command with the same flags”

Repeating Pattern

This is a known trick, so some of you smart kids might have thought, seeing the above example, that you could instead do:

:%s/pattern//gn
:%s//repl/g

And you would have been quite right, as leaving an empty pattern will cause Vim to use the last pattern. A word of caution though: this “last pattern” is the last search pattern!

This search pattern can be set by 3 different commands:

  • A / search
  • A :substitute command
  • A :global command

This means that :g/pat/s//repl/g is strictly equivalent to :%s/pat/repl/g, and that in the below scenario:

:%s/pat1//gn
/pat2
:%s///gn

The pattern used in the last command will in fact be pat2 and not pat1.

So what if you wanted to use the last substitute pattern instead? This is where the earlier :& comes into play: in the above example, replacing the last command with :&gn (or :&&) will use pat1 instead of pat2. Another interesting property of :& is that it will not set the search pattern, so using :%s///gn behind it would still use pat2 (but it counts as being the last substitute… Vim has many dark corners).

This is was a pretty complicated section, so let’s try to summarize it before moving on:

  • There are 2 main ways to repeat the previous pattern: using an empty pattern and using :& (or :s)
  • The only way to repeat the pattern with a different replacement is to use an empty pattern
  • If the last search was done with a :substitute or :global command, then :s//~/ (~ means “repeat the last replacement”) and :& are equivalent
  • If the last search was done with a /, then using an empty ‘pattern’ will use that last search, and :& will use the pattern of the last :substitute or :global

Repeating Replacement

If you want to repeat the previous replacement but change the other parts, you can simply use ~. For instance, if you run :s/pat1/replacement/ then :s/pat2/~/, the second command will replace pat2 with replacement.

That’s it. Compared to the complexity of pattern repetition, it is quite straight forward.

Special Replacements With \=

So far, we’ve seen how to repeat searches, and how to leverage flags like n to do more with :substitute. But Vim would not be Vim if it did not have more tricks up its sleeve! In this last section, I’ll introduce what is probably the most powerful feature of :substitute: special replacements with \=.

In regular substitute commands, the replacement part is interpreted as a string (with some exceptions, like \1 or \r). If replacement starts with \= however, then whatever follows in interpreted as an expression.

What does that mean? Let’s take a very simple example, and say you had the following function:

function! Hello()
  return "hi"
endfunction

Then :s/pat/Hello() would replace pat with Hello() (the literal text), while :s/pat/\=Hello() will actually evaluate the Hello() function and replace pat with hi.

Use Cases

Honestly, you probably find an infinite number of use cases, since it gives you access to (almost) the full power of Vimscript in your replacement commands (and even access shell commands through system()!).

Here are some things I’ve been using it for to give you some ideas.

Expand Some Variables

:s@\v\w+\.vim@\=expand("%:p:h")."/".submatch(0)@g

The above will replace each my_file.vim with /full/path/to/current/file/my_file.vim.

For reasons why @ is used instead of /, see the gotchas.

Perform Double Substitutions

Another cool thing about \= is that you can use substitute in it too, and perform a second substitution (based on the value of a pattern matched by the first substitution for example).

Let’s consider a simple case. Assuming a line like:

{a:a, b:b, c:c}

Let’s convert it into:

{
  a:a,
  b:b,
  c:c
}

A first try could be:

:s/\v(\w+:\w+%(,|\})) */\r  \1/g

This is basically saying “insert a newline and two spaces before each something:something, or something:something}, discarding all spaces between patterns” (see the end of this section for a breakdown of the command).

The result of this command will be:

{
  a:a,
  b:b,
  c:c}

Which is pretty close to the desired result. The only missing thing we wish to tell Vim is “insert a additional new line after the pattern if that pattern ends with a }”.

This final hurdle cannot be cleared without using \=, but using it makes it rather straightforward. Indeed, running the following will give us the desired result

:s/\v(\w+:\w+%(,|\})) */\="\n  " . substitute(submatch(1), "}", "\n}", "")/g

Let’s break it down:

  • pattern: \v(use “very magic”)((start sub expression)\w+:\w+(one or more word characters, followed by a column then one or more word character)%((start a pattern not counted as a sub expression),|\}(a comma or a closing bracket)))(close both the pattern and the sub expression)*(0 or more spaces)
  • replacement: "\n "(a new line and two spaces).(concatenation)substitute(submatch(1), "}", "\n}", "")(output the matched pattern, replacing any closing bracket by a new line followed by a closing bracket)

Of course, you could probably have solved this particular problem differently, but you get the idea!

Gotchas

There are a few things to keep in mind while using \=:

  • All “special characters” like \1, \r (see :help sub-replace-special) lose their meaning. To insert a new line, use "\n", and to refer to sub-matches, use submatch(i) (i taking values from 1 to 9). The full match is accessible via submatch(0)
  • If the result of the expression after \= is a list, then each element will be outputted as a new line (in addition to any "\n" that might exist in each list element!)
  • The separation character must not appear the expression (but can appear in the result of the expression), so :s/pat/\=expand("%:p")."/"/ is invalid (but :s/pat/\=expand("%:p")/ is ok), which is the reason why @ was used as a separator instead in the first use case

One Last Mapping…

For dessert, here is perhaps the most convenient shortcut to repeat substitutions: the g& mapping (you don’t even need to type the :!). It is an alias for :%s//~/&.

You should be able to understand what it does and its implications now. If you don’t, read back the previous sections!


Thank you for sticking with me until the end! Please shoot me a message, or tweet @nicol4s_c if you want to chat about any of this, if you spotted any mistakes or typos, or if you want to show me other cool vim tricks! Have a great day :)