Return of the Mac
In a previous article, I talked about vim macro basics. In that article, I described how to record a custom macro, assign it to a key and then use it to make automated edits to a BIND zone. I also teased that I would cover more advanced uses of macros, like nested macros, in a future issue. I took a brief detour to cover a few different topics, but now I'm back on topic, and in this article, I discuss more complicated uses for macros.
I like using BIND zone files for macro examples, because it's the file I most often use macros in myself. Because multiple people often edit zone files, they may not all have the same formatting. Plus, the top of a zone file generally has a different multi-line format compared to the rest of the file. In my July 2014 article, I talked about how to add 50 sequential A records in a zone file using a single macro, but when I have to perform more complicated edits, or if I have to perform edits selectively in some files but not others, I've found it useful to record a few different simple macros under different keys, then record a new macro that just calls those other macros in a particular order. Among other things, this lets me change some of the shorter macros if I need to, without having to re-record everything.
For the first example, let's look at a more complete version of the BIND zone file I used last time:
;
; BIND data file for example.com
;
$TTL 15m
@ IN SOA example.com. root.example.com. (
2014081500
10800
1200
7200
7200 )
;
example.com. IN NS ns1.example.com.
example.com. IN NS ns2.example.com.
;
ns1 IN A 10.9.0.5
ns2 IN A 10.9.0.6
;
worker1 IN A 10.9.0.15
worker2 IN A 10.9.0.16
worker3 IN A 10.9.0.17
. . .
worker50 IN A 10.9.0.64
In this example, let's say I have 15 zone files for different zones, but I want to make the same set of edits to all of them. I want to change the TTL in the file to be 10 minutes, and I need to change the contact info for the domain from root@domain to dnsadmin@domain. I also need to increment the serial number (2014081500) in the zone file, and I need to change the name server IPs all to point to a new set of name servers at 10.1.0.250 and 10.1.0.251, and finally, I want to add 50 more workers to each zone file.
Although I imagine I could build all of this into a single big macro, for me, it makes sense to split up the steps into at least four macros that I already have pre-assigned letters to:
-
Macro t will change the TTL.
-
Macro s will change the contact information and increment the serial.
-
Macro n will update the name server records.
-
Macro w will add one more worker.
Because I'm going to chain these macros together, it's even more important
than in the past that I make sure my cursor is anchored in a known location
first. For the first macro, this means starting with
gg
to move to the very
top of the file, while for the last macro, I will want to type
G
to move to the
bottom of the file. At each phase, it's incredibly important that you test
each macro, then undo the changes and confirm that your macros work exactly
how you expect. Let's break down each macro.
For macro t, I first type qt
to enter macro mode and assign the macro to the t
key. Then I type gg
to make sure I'm at the top of the file. Next I type
/TTL
<Enter> to move the cursor to the TTL line.
Then I type w
to move forward
a word to the actual TTL value I want to change, and then I type
cw10m
to change
the following word from 15m to 10m. Finally, I press Esc to exit insert mode,
and q
to exit the macro. The complete set of macro keystrokes then would be
qtgg/TTL
<Enter> wcw10m
<Esc> q
.
Once I record the macro, I type u
to undo
my changes, and then test the macro by typing @t
.
For macro s, I type qs
to enter macro mode and assign the macro to the s key.
Then I type gg
again to anchor to the top of the file. Next I type
/SOA
<Enter> to move to the SOA line. Then I type
/root
<Enter> to move to the
beginning of root.example.com, and then type
cwdnsadmin
<Esc> to change that word
to dnsadmin and exit insert mode. Next I type ^j
to move the cursor to the
beginning of the following line. Finally, I type
w
<Ctrl-a> to move forward to
the serial number and increment it, and then q
to exit macro mode. The complete
set of macro keystrokes becomes
qsgg/SOA
<Enter> /root
<Enter> cwdnsadmin
<Esc>
^jw
<Ctrl-a> q
. And again,
I save the
macro and use u
to undo the change and replay it
with @s
to make sure it does
what I expect.
For macro n, I type qn
to enter macro mode and assign the macro to the n key.
Then I type gg
to anchor to the top of the file.
Next I type /^ns1
<Enter> to
move to the line that configures the name servers. At this point, there are a
few ways I could edit these lines. My preference is to type /IN
A
<Enter> 2w
,
which will move my cursor to the beginning of the IP. Then I type
c$10.1.0.250
<Esc> to edit to the end of the line and exit insert mode.
Since
ns2 is so similar to ns1, I can just type
yyp
<Ctrl-a> $
<Ctrl-a> to copy and
paste ns1, increment ns1 to be ns2, then move to the end of the line and
increment the IP. Next I need to find the existing ns2 line and delete it
with /^ns2
<Enter> dd
.
Finally, I can type q
to save the macro. The complete
macro is qngg/^ns1
<Enter> /IN
A
<Enter> 2wc$10.1.0.250
<Esc>
yyp
<Ctrl-a> $
<Ctrl-a>
/^ns2
<Enter> ddq
.
Although this
seems like a lot of text, it will save a ton of work when you have to repeat
the steps on multiple files.
For the final macro w, I type qw
to enter macro mode
assigned to the w key,
and then type G
to move to the
bottom of the file this
time. Then I type
/^worker
<Enter> N
to search for the next worker (which will
wrap around to the top, then N
will move back to the last worker in the
file). Finally, I type yyp
to copy and paste the
line, then <Ctrl-a> $
<Ctrl-a>
to increment both the worker hostname and the IP. Finally, I type
q
to exit
macro mode. The complete macro is then
qwG/^worker
<Enter> Nyyp
<Ctrl-a> $
<Ctrl-a> q.
Like the others, I
test it out with @w
a couple times, and use
u
to undo all the changes
in between until I am satisfied that it works.
Now that I have all of these macros recorded, I could just open each file
and type @t
to update the TTL,
@s
to edit the contact information and serial,
@n
to
update the name servers, and type 50@w
to add 50
more workers.
Because I'm
going to perform these same steps on a number of files, I might as well
capture all those commands in a new macro I'll assign to c. To do that, I just
type qc
to enter macro mode assigned to the c key,
then type @t@s@n50@w
to
perform all of the previously recorded macros. Finally, I type
q
to exit
macro mode. The complete set of keystrokes is
qc@t@s@n50@wq
to assign all of
the above sets of keystrokes to a single macro. Now when I open the next
file, I can just type @c
to perform the complete list of steps.
Now besides saving a few keystrokes, there are other good reasons to nest macros in this way. Because I saved each logical step as its own macro, I can tweak or modify any of the above macros independently, save the new macro to the same key, and all of the other macros, including the final combo macro can stay the same.
Let's say that after I recorded all of these macros, I
realized I got the IP address for the name servers wrong. All I would
have to do is record a new macro assigned to the same n key, and once I was
done, I still could use @c
to make the complete set of changes to a file.
I hope you find these examples useful, and that the next time you have to perform a series of mundane edits to many text files, you'll save some keystrokes with vim macros.