2019-10-03
Making changes to multiple files at once in Vim
We recently finished switching the last of our machines to a different client for Let's Encrypt, and as part of that switch the paths to our TLS certificates had to be updated in all of the configuration files using them. On a lot of our machines there's only a single configuration file, but on some of our Apache servers we have TLS certificate paths in multiple files. This made me quite interested in finding out how to do the same change across multiple files in Vim. It turns out that Vim has supported this for a long time and you can go about it in a variety of ways. Some of these ways expose what I would call a quirk of Vim and other people probably call long-standing design decisions.
Under most circumstances, or more specifically when I'm editing
only a moderate number of files, the easiest thing for me to do is
to use the very convenient ':all
' command to open a
window for every buffer, and then use ':windo
' to apply a command
for every window, eg ':windo %s/.../.../
'. Then I'd write out all
of the changed buffers with ':wa
'.
The Vim tips on Search and replace in multiple buffers and
Run a command in multiple buffers
also cover ':bufdo
', which runs things on all buffers whether or
not they're in windows. That was in fact the first thing I tried,
except I left off the magic bit to write out changed buffers and
Vim promptly stopped after making my change in the first file. This
is what I consider a historical quirk, although we're stuck with
it.
Vi has long had multiple buffers, but it's always been pretty stubborn about getting you to write out your changes to the current buffer before you moved to another one. It's easy to see the attraction of getting people to do this on the small and relatively loaded machines that vi was originally written on, since a clean buffer is a buffer that you don't have to retain in memory or in a scratch file on disk (and it's also not at risk if either vi or the machine crashes). However, these days it's at odds with how most other multi-file editors approach the problem. Most of them will let you keep any number of modified buffers around without complaint, and merely stop you from quitting without saving them or actively discarding them. Not hassling you all of the time makes these editors a bit easier to use, and Vim is already a bit inconsistent here since windows are allowed to be changed without preventing you from switching away from them.
Given my views here, I probably want to set 'hidden
' to
on. Unless I'm very confident in my change, I don't want to add '|
update
' to the ':bufdo
' command to immediately write out updates,
and as noted 'hidden
' being on makes Vim behave more like other
editors. The drawbacks that the Vim documentation notes don't apply
to me; I never use ':q!
' or ':qa!
' unless my intention is
explicitly to discard all unsaved changes.
It's possible to do this in a trickier way, with the ':hide
' command.
Because I just experimented with this, it should be done as:
:hide bufdo %s/.../.../
I don't think there's a perfect way to undo the effects of a
multi-file operation that didn't work out as I intended. If all
buffers were unchanged before the bufdo
or windo
, I can use it
again to invoke undo in each buffer, with ':bufdo u
'. With unchanged
buffers, this is harmless if my cross-file operation didn't actually
change a particular file. If there are unsaved changes in some
buffers, though, this becomes dangerous because the undo in each
buffer is blind; it will undo the most recent change whether or
not that came from the first 'bufdo
'.
(All of this tells me that I should carefully (re)read the Vim buffer FAQ, because how Vim does buffers, files, tabs, and windows is kind of confusing. GNU Emacs is also confusing here in its own way, but at least with it I understand the history.)
On the whole, ':all
' and then ':windo ...
' is the easier to
remember and easier to use option, and it lets me immediately
inspect some of the changes across all of the files involved.
So it's likely to be what I normally use. It's not as elegant
as the various other options and I'm sure that Vim purists will
sigh, but I'm very much not a Vim purist.
(This is one of those entries that I write for my own future reference. Someday I'll write a page in CSpace to collect all of the Vim things that I want to remember and keep using regularly, but for now blog entries will have to do.)