Vim: Macros, Substitutions, the Global Command
It's natural to think "substitutions" and "regular expressions" to repeat the same change. There's an even more natural way: recording typed characters.
1 Substitutions
They are carried out with the :substitute
(:s
for short) command. Say we've got the following text file:
foo
bar
baz
boo
... and we'd like to swap the first 2 characters of each line containing b
. We'd be tempted to think substitutions and craft a clever regular expression along the lines of:
:%s/\(b\)\(.\)/\2\1
This is a trivial example, and my solution is only simple because I happen to know that lines with a b
in them actually start with a b
. So I cheated. This wouldn't have worked if one of the lines had had e.g. obo
in it. And if the file had been much longer, I wouldn't have been able to check that there wasn't one. Coming up with a substitution involving a regular expression matching a b
anywhere on the line and defining the groups to swap them would require more thinking. And in real life things can become much more contrived still.
2 The Global Command
The :global
(:g
for short) command can already make life a bit easier. It executes an Ex command on a range of lines matching a pattern. It can for instance be used to delete lines. This deletes all the lines matching b
:
:g/b/d
The reverse global command, :vglobal
(:v
for short) executes an Ex command on a range not matching the pattern. It's probably worth stopping for a minute thinking about what is meant by Ex commands. It's those commands that you type into the command line. The :d
Ex command (short for :delete
) we use here may not seem particularly familiar. It's just that we don't normally come across this one very often because we just use its Normal mode counterpart invoked by simply typing d . Perhaps a more meaningful example then is a :global
command line which uses the :substitute
command. So let's come back to our original example of swapping the first two characters of lines with a b
in them:
foo
bar
baz
boo
obo
This time, we can craft a much more generic :substitute
command such as :s/\(.\)\(.\)/\2\1
without worrying about targeting only lines with a b
in them, because we'd leave this to the :global
command. This, then, would work beautifully:
:g/b/s/\(.\)\(.\)/\2\1
But there's an even simpler and much more readable approach which involves using the :normal
(:norm
for short) command, to execute Normal mode commands like they are typed:
:g/b/norm xp
That is, x for deleting the character under the cursor and p for putting it back after the cursor.
3 Recording Typed Characters
The :global
command makes it easy, especially when jointly used with the :normal
command. But it can get even easier by simply recording typed characters. Type q a to start recording characters into the e.g. a
register and just do what you would normally do to find a b
, move to the start of the line, swap the two characters and move to the next line: / b Enter 0 x p . End the recording with q . Then repeat as many times as you like by repeatedly typing @ a , which you can make many times simply by typing e.g. 9 9 9 @ a .
Apart from its simplicity of operation, what's nice about this approach is that you can make it as interactive as you like and see the effect it has. The downside however is that it's undoubtedly slower to run than using :substitute
or :global
, not least because all the operations are drawn. You can improve this by turning off redraws:
:set lazyredraw