Suppose you need to recursively search your docs-as-code (or other static site) files for some keyword, and you want to exclude some file types or directories. Perhaps you think to yourself, I’ll use
grep to solve this problem.
You know there must exist a correct command-line incantation, but you’re not a wizard. As a mortal, you know that
find’s syntax is unusual and searching Grep’s man page for the right flags is tiresome at best. It’s a lot to keep in your head for a seemingly simple task.
What’s worse, many traditional tools aren’t strictly compatible between systems. A Mac’s
grep (from BSD) is slightly different from a Linux machine’s
grep (from GNU). Your skills in one environment might not do you any favors in another.
Wouldn’t it be nice if there was a faster, more convenient, and easier-to-configure code search utility? I’ve got good news: there are several of them that you can try!
There are several Grep-like code search tools that are worth your time, including:
While Grep is a general-purpose search tool, these alternatives set themselves apart with conveniences for human users searching code. They offer perks such as:
In particular, I’m a fan of ripgrep, but each offers a similar set of benefits over the traditional tools.
Here’s an example: suppose I want to get a list of all the Markdown files in a repository containing a specific word. To demonstrate, I’m going to search a project I contribute to a lot, mdn/browser-compat-data, for the string
schema. Historically, I might’ve started with something like this:
$ grep --recursive --files-with-matches 'schema' --include='*.md' . # For clarity, I’ve used the long form for all options in this article. # In practice, I’d probably use the shorter, more cryptic form: $ grep -lR --include='*.md' 'schema' .
This works, but perhaps a bit too well. The results look like this:
./GOVERNANCE.md ./node_modules/better-ajv-errors/README.md ./node_modules/js-yaml/CHANGELOG.md ./node_modules/js-yaml/README.md ./node_modules/json-schema-traverse/README.md ./node_modules/ajv/README.md ./docs/data-guidelines.md ./docs/workflow.md ./docs/testing.md ./docs/contributing.md ./schemas/browsers-schema.md ./schemas/compat-data-schema.md ./README.md ./RELEASE_NOTES.md ./.worktrees/meta/node_modules/js-yaml/CHANGELOG.md ./.worktrees/meta/node_modules/js-yaml/README.md ./.worktrees/meta/node_modules/json-schema-traverse/README.md ./.worktrees/meta/node_modules/eslint/CHANGELOG.md ./.worktrees/meta/node_modules/ajv/README.md ./build/README.md
There are a few problems with this search:
The results include lots of non-project files from my dependencies, which are the lines starting with
node_modules. I don’t care about those files because they’re not my project files. In fact, they’re excluded from my repository with a
The results don’t include some of the files I was most interested in because the defaults assume a case-sensitive search, which I don’t want. Though it doesn’t show in this output, the results include only files containing
schema and not
I have to specify a recursive search on the current directory (the
--recursive option, plus the trailing dot). This is by far the most common way I search, but I have to remember to do it every time (or invent an alias, and remember a new command).
I can fix these problems, but the resulting commands get complex. Here’s a few more attempts to work my way to searching only the repository files, case insensitively:
# Case insensitive, but still includes ignored files $ grep --recursive --files-with-matches --ignore-case --include='*.md' \ 'schema' . # Case insensitive, ignores files, and # I only needed three separate commands to get there! $ git ls-files "*.md" | \ xargs grep --files-with-matches --ignore-case 'schema'
There are probably other routes to my desired search—maybe even simpler ones—but the real trouble is in inventing a new search, every time I search.
By comparison, ripgrep has two qualities that I love: it’s friendly with its defaults and it’s easier to configure when the defaults aren’t to my liking. Often,
rg some_search_string gets me exactly the results I’m looking for, the first time. But even more complex tasks, such as the search for “schema”, are straightforward.
Here’s what it looks like with
rg, with its defaults:
$ rg --type=md --files-with-matches --ignore-case 'schema' # shorter, more cryptic version $ rg -lit md 'schema'
As it comes, ripgrep’s flags and options are very similar to Grep’s, so I didn’t need to learn many new ones. But already, I get the benefit of:
.gitignore, by default
(It’s a lot faster too.)
There’s one thing I don’t love about the defaults though, which is that I have to specify a case-insensitive search. There’s lots of ways to deal with this problem, such as using an alias (which, to be fair, I could’ve done for Grep). But I can go a step further and get something even nicer to use by combining two of ripgrep’s niceties.
ripgrep lets me set default flags in a configuration file. ripgrep also supports an option,
--smart-case, that ignores case unless there’s an uppercase character in the search pattern. Taken together, I can make my default search exactly what I wanted in the first place: case-insensitive, recursive, and ignoring non-project files. And I can check my configuration file into version control for portability to all of the computers I use on a regular basis.
$ rg --type=md --files-with-matches 'schema'
As a writer who spends a lot more time searching and reading docs than I do actually writing new words, that’s not a trivial improvement.
ripgrep isn’t the only option and other choices have their merits. For instance,
git grep is a convenient tool that’s more often available by default than ripgrep (which is rarely available by default).
And Grep itself isn’t going anywhere. Traditional command-line tools have nearly a half century of history for a good reason. Grep works better in lots of situations, such as scripts that can’t rely on less common tools or when you’re logging into a server briefly and can’t be bothered to install an alternative.
ripgrep’s author doesn’t claim to be a true replacement for Grep. I really like ripgrep’s answer to the question, Can ripgrep replace grep?
Yes and no.
If, upon hearing that “ripgrep can replace grep,” you actually hear, “ripgrep can be used in every instance grep can be used, in exactly the same way, for the same use cases, with exactly the same bug-for-bug behavior,” then no, ripgrep trivially cannot replace grep. Moreover, ripgrep will never replace grep.
If, upon hearing that “ripgrep can replace grep,” you actually hear, “ripgrep can replace grep in some cases and not in other use cases,” then yes, that is indeed true!
(Hats off to ripgrep for documenting when not to use it, which might be my favorite genre of technical documentation.)
The answer is about ripgrep, but it could probably stand in for any grep alternative. These tools make sense in lots of situations, but not every situation. If you need the ubiquity and compatibility of Grep, then accept no substitutes. For everything else, ripgrep and friends are there for you.
There are a bunch more benefits of ripgrep I could talk up—prettier output, built-in replace, and more—but since you got this far, you may as well install it to see for yourself.