Demystifying Multi-file Searches in Vim (and the command line)

Vim multi-file searches don't have to make you twitchy. Ethan has tips to make you search confidently.

My confidence has always been a little shaky when it comes time to search through multiple files for a word in Vim. I’ve known how to do it, but it rarely proves to be a comfortable experience.

"Is this pattern I'm typing interpreted as a regex, or a literal string? Do I need quotes? Am I searching through files starting from my current directory or from the directory of the file I'm currently editing? Should it be taking this long? How does :grep differ from :vimgrep or :lgrep or even grep in the command line? Am I really finding every instance of what I'm looking for? And why’s this so difficult in Vim but so easy in every other decent text editor?"

I’d end up walking away with results, yet feeling like I didn’t do it quite right.

It shouldn’t be this way. This is something I do on a daily basis. Searching through multiple files should be convenient, not scary. In this post, we’ll explore searching through multiple files in vanilla Vim.

Let's start by taking a couple steps back and looking at why things work this way.

Why’s It So Weird in Vim, Anyway?

Vim follows the tried-and-true Unix philosophy of "Write programs that do one thing and do it well. Write programs to work together." Vim edits text. That’s its job. That’s what it’s good at.

When Vim needs to do something outside of its responsibilities, it outsources that heavy lifting to the proper utility and then jumps back to what it does best: editing text. For this post, the "heavy lifting" we're talking about is sifting through lots of files for a pattern, and the “proper utility” we're talking about is grep.

Grep on the Command Line

Grep is a Unix command that lets you search through multiple files for a pattern. Grep was first published in 1984, and it’s pretty much guaranteed to be included on any Unix box you might find yourself on. When you search multiple files in Vim, it’s really just grepping under the covers. So it makes sense to familiarize ourselves with grep on the command line and build up from there.

Searching Through All Files

I'm a frontend developer, and I do a lot of searching for CSS class names. For this example, let's say we're fixing a styling bug on our website's ads. It looks like there might be something wrong with our style definition. We want to search through our project for the selector .ad.

From the root of your project, you can search through all your files recursively from the current directory like so:

grep -R '.ad' .

The -R flag is telling grep to search recursively. The . is telling it to start in our current directory.

We don't necessarily need those quotes surrounding our pattern. You only need them if your pattern includes characters that might confuse your shell. I like to include them just to play it safe.

In our small, fictional sample project, grep returns these results:

./css/ad_styles.css:.ad {
./css/ad_styles.css:   * because IE is bad */
./css/ad_styles.css:.small_ad {
./css/deprecated/ad_styles.css:.ad {
./js/script.js:// in case coffeescript is a fad
./js/script.js:// glad this works
./js/script.js:// trigger .ad

What you're seeing is a list of all the lines that match what we looked for, preceded by the file path that they reside in.

Limiting Searches to Files with a Specific Extension

We're able to browse over this list of results and see where this selector occurs. On a large project, however, this list will be much bigger. Let’s do a more focused search for what we want. We’re looking for styles, so we really only need to search through CSS files. Here’s how we do that:

 grep '.ad' **/*.css

That **/*.css will look through every file ending in .css. That double star has the side effect of being recursive, so we can drop the -R flag.

The above search yields these results:

css/ad_styles.css:.ad {
css/ad_styles.css:   * because IE is bad */
css/ad_styles.css:.small_ad {
css/deprecated/ad_styles.css:.ad {

You might think the second and third results are strange. Why is it matching because IE is bad and .small_ad? It's important to keep in mind that grep, by default, matches regular expressions.

Oh My Glob

Do all those asterisks make you uncomfortable? Or did the above command not work? Depending on your environment, your shell might not support this globbing syntax. If that’s the case, you might need to do things in a more bulletproof way.

Searching for Strings Instead of Regular Expressions

The way I use grep, 90% of the time, it's more intuitive to match literal strings as opposed to regular expressions. To do so, pass grep the -F flag.

grep -F '.ad' **/*.css

This narrows things down perfectly, and returns these results:

./css/ad_styles.css:.ad {
./css/deprecated/ad_styles.css:.ad {

Grepping Inside Vim

If you can grep on the command line, you can grep in Vim. Usage between the two looks nearly identical.

:grep -F '.ad' **/*.css

But grepping in Vim comes with some bonuses over the command line.

grepping in vim

After running the above command, we’re whisked away to a buffer looking at the first matched pattern. Vim ran a grep search behind the scenes, parsed the results, and populated your quickfix list with all those matches. You can use :cn and :cp to go browse forward/backward through the quickfix list, :copen to see it and :cclose to close it.

I recommend using Tim Pope’s unimpaired.vim plugin or at least stealing his bindings for any prolonged exposure to the quickfix list.

What about :vimgrep and :lgrep and :lvimgrep?

:vimgrep is a method of searching through files that uses Vim’s own pattern-matching engine instead of grep. This is the same engine that’s used for in-file / searches. This lets you write a search pattern, test it within a file, then use that same pattern to look through many files. This is going to be slower than your run-of-the-mill grep. Since most of my multi-file searching is either fixed strings or very simple regexes, I rarely use this search method. Don’t worry about it unless it sounds useful to you.

The :lgrep and :lvimgrep commands do the same thing as :grep and :vimgrep, except instead of populating the quickfix list, it populates the location list. Again, unless this sounds useful to you, you don’t need to think about it.

Beyond Grep

Everything we talked about in this post is part of vanilla Vim. You can do this stuff with any installation of Vim you might be using. You can, however, extend Vim’s search capabilities with the use of other apps.

Are your searches slow? Have a need for more speed? Check out The Silver Searcher, an alternative to grep that is specifically tuned for searching through code. This article by Dan Croak will show you how to override Vim’s internal grep program with The Silver Searcher.