Brendan Dawes
The Art of Form and Code

What the Ack! Multi-file Find and Replace with Vim and Ack

Occasionally it's handy to be able to do a find and replace across multiple files, or even insert something new across many files. Using Vim with the command cdo and the Ack search tool makes this kind of thing not only easy but very powerful.

Install Ack

Before I get into the details, we first need to install Ack. Ack is like grep but optimised for searching source code. If you hop on over to the project page you can find a version of Ack to install for your system. As I'm on a Mac, I went the Homebrew method. Once installed it means you have the power of Ack in the Terminal across your whole system. We're not going to worry about using Ack in the Terminal but concentrate on using it inside Vim.

Install ack.vim

To use the power of Ack inside Vim, we can install the ack.vim plug-in. Follow the instructions on how to install it using your favourite Vim plug-in manager, such as Vundle or similar.

Using Ack inside Vim

With the plug-in installed, launch Vim, navigate to a directory you want to search, preferably with lots of files and then type:

    :Ack! some-word-you-want-to-search-for

Replacing the some-word-you-want-to-search-for with a word to search for. Hit enter and a window will appear — called the Quickfix Window — with a list of files that contain the word you searched for. Use the j and k keys to navigate up and down the list and press Enter to open that file in a new buffer above.

If you want to ignore a directory you can add --ignore-dir=dir-to-ignore after the Ack! command. Oh, the exclamation mark stops Ack from opening the first result automatically. To close the Quickfix window just type :ccl. Open it again with :copen

Basic Mult-file find and replace

That's the very basic usage of Ack inside Vim. But we want to use it to do a site-wide find and replace. To do this we use a Vim command called :cdo. Using this, Vim will execute any command we want on every file listed in the Quickfix window.

To demo that, I've got a bunch of directories containing files that make up the examples for the Dawesome Toolkit — my library for Processing. At the top of each of these files is some boiler plate in the form of comments, detailing the name of the library together with where on the web it can be found.

    // The Dawesome Toolkit — A Library for Processing by Brendan Dawes
    // http://cloud.brendandawes.com/dawesometoolkit/

Let's suppose that I decide to change the location of this library to its own domain — say from cloud.brendandawes.com to dawesometoolkit.com. How do I do this across all the files in an easy way?

First we use Ack to do a search for the term I'd like to change:

    :Ack! --ignore-dir=build-tmp cloud.brendandawes.com

Notice I add an ignore-dir flag to ignore the build-tmp directories which reside in each example directory.

I now have a Quickfix window containing a list of files matching my find query. Now I can use that list to have Vim work through each file and perform a command on each.

To do this I use the cdo command together with the substitute command:

    :cdo s/cloud.brendandawes.com/dawesometoolkit.com/g | update

On pressing Enter, Vim will work through the Quickfix list, perform the above substitution, before saving the changes using the update command. Job done. Now for a well-deserved cup of tea. It's the afternoon, so Earl Grey will do nicely.

Using Macros across Multiple Files

Because we can perform any command we want with cdo, we can even use Macros to do more complex things. In the past we may have had to construct some complex regular expression — and they always make my head hurt. Macros on the other hand are lovely.

Let's say I want to insert Version 1.2 as a comment on the third line of each of these files. So, not a find and replace but instead inserting something new. Firstly I record a macro like this:

    - Type 'q' to start recording the macro
    - Type 'a' to record the macro into the 'a' register
    - Type :3 and hit enter to position the cursor at the third line
    - Type 'O' to insert a new line above line 3 and go into Insert mode
    - Type // Version 1.2 
    - Press Esc
    - Type 'q' to end recording the macro

Now I have a macro I can trigger by pressing @a, which will insert // Version 1.2 as a comment on the third line. All I need to do now is use the same Quickfix list as before to run this macro on each of those files with cdo like this:

    :cdo normal @a | update

By using normal it will perform a normal command as if I was typing it myself, only on every file in the Quicklist, and use update to save the changes.

This is a simple example of using cdo with macros, but hopefully you can see how powerful it might be when you're trying to do more complex things.