Fixing Files with Vim Macros

June 15, 2007 at 10:51 PM | Vim | View Comments

I got an email a couple days ago asking me to add a small header to all the pages of a website I work on from time to time. The site was built with static HTML a couple of years ago and this was the first site-wide modification I've had to make since then. So I fired up Vim, loaded the session I use to work on the page and got to work. It didn't take too long to make the changes to one page -- add a couple of JavaScript functions, add a call to the the body's onLoad property and finally a little div just below that.

But then came the problem. How do I apply this change to all the pages? Normally, to do multi-file search-and-replace, I use perl -pi -e 's/search/replace/g' `find . -name '*.html'`... But, in this case, I would have to run that three times (one for each block of text)... And more importantly I want to see the file before I edit it (because some of the HTML files don't use the standard template, so they would get horribly mangled).1

So how did I do it? By writing a Vim script! I used gvim *.html to open up all the HTML files, them qa to record all my keystrokes in to register a.

As I made the changes, I used general steps so that they could be repeated on each file. For instance, I used /onLoad to search for the onLoad property of the body, then f=la to find the =, move one character to the right then insert my text. When that was done, I opened up the line right below the body tag with o and put in the div tag that I needed.

Finally, after saving the file, I used :bn to move to the next buffer and hit q to stop the recording. I could now look at the new file to see if it needed to be changed, then change it with @a or move on to the next buffer with :bn.

Now, this makes me curious... Faced with a similar situation, how would you solve a problem like this?

1: Now, in retrospect, I realize I could have used:

for FILE in `find . -name '*.html'`; do
  read -p "Edit $FILE? " R
  if [[ $R == "y" ]]; then
     perl -pi -e 's/old/new' $FILE

But that would assume that I was putting some serious level of thought in to this. Also, unless I also had the presence of mind to add cp $FILE{,.backup} I would not have any way to un-do my changes.