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 pattern
… for 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
forall
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
forlast
to substitute this match and stop (when you realize you don’t want to replace anything else), andq
forquit
to stop (likel
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 emptypattern
and using:&
(or:s
) - The only way to repeat the
pattern
with a different replacement is to use an emptypattern
- 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 thepattern
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, usesubmatch(i)
(i
taking values from1
to9
). The full match is accessible viasubmatch(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 :)