Beyond Substitutions with Vim
Regular expressions make the Vim :substitute command remarkably powerful. It comes with nifty and unexpected tricks making it essential in any user toolkit.
1 Substitution Basics
The :substitute
command (:s
for short) matches strings from patterns and replaces them with something else, much like the Find/Replace function you'd find in text editors, word processors and other programs dealing with text. In essence:
:substitute/text to change/text to change it to
As with many other Vim commands, you run :substitute
on the current line or over specific ranges, for instance $
(the last line of the file), %
(the entire file), '<,'>
(a visual mode area), ...
Vim's pattern language makes it a versatile utility. In addition to being able to match just about anything, you can for instance base the replacement string upon the matched string, by grouping portions of it and referring to these groups. To swap two characters in a file of two-character lines:
:%substitute/\(.\)\(.\)/\2\1
2 Flags and Counting Occurrences
The :substitute
command accepts a number of flags at the end the command line. Common ones will be c
to confirm each change and g
to replace all matches in the line (otherwise only the first one will be substituted):
:substitute/text to change/text to change it to/cg
For more specific purposes, I quite like using the n
flag too, which changes :substitute
's basic role; instead of replacing text, it will only count the number of occurrences. The g
flag has an effect there too: on a line such as 1 2 3 4
, :s/\d//n
will report 1 match whereas :s/\d//ng
will report 4 since it goes to the end of the line. Of course, n
is best used for carefully-selected ranges, whether they be the whole file (%
), a visual mode area ('<,'>
) or just the single line.
3 Evaluating Expressions
The substitution string can have a special role too. When it starts with \=
, it's evaluated as an expression. I've used this once not so much to perform any substitution, but to collect matched strings into an array. Here, I'm collecting numbers from lines of text, wherever they are:
:let numbers = []
:%s/\d\+/\=add(numbers, submatch(0))/n
After initialising a list, I run :substitute
over the whole (%
) file. Only, this won't really be a substitution because I don't really want to change anything, I only want to match and collect specific portions of my file. So I add the n
flag right at the end of the command. The pattern I'm trying to match against with \d\+
is a sequence a digits, as long as it can be. The expression I'm evaluating with \=
is add(numbers, submatch(0))
, i.e. add the match to the numbers
list. The :substitute
command makes the matched text accessible with the submatch()
function. Where the 0
parameter has it return the matched string, other parameters such as 1
, 2
or 3
will have it respectively return the first, second or third group. Suppose we run that substitution on this file:
f17oo
bar23
37baz
bo42o
... subsequently running :echo numbers
will indeed print out ['17', '23', '37', '42']
.