Skip to content
Pipelines and Pizza 🍕
Go back

RipGrep > grep: Why You Should Switch

5 min read

Monday’s post opened with an audit of this blog’s own repo — 39 broken internal links, a “required” frontmatter field that 28 of 45 posts didn’t have, three eras of contradictory documentation.

Nobody found those by reading 45 files. One tool found them, run about a dozen different ways.

Today’s idea: ripgrep’s real advantage isn’t that it’s fast — it’s that by default it searches your source code instead of your dependencies. Speed is the thing everyone leads with, and it’s the least interesting part.

The Default Is the Feature

rg reads your .gitignore and skips what it says to skip. It also skips hidden files, binaries, and .git/ — with no configuration, no wrapper script, no --exclude-dir incantation you paste from an old shell history.

Here’s what that means on the repo that builds this site:

$ rg --files | wc -l
     173
$ find . -type f -not -path "./.git/*" | wc -l
   19563

Same directory. One tool sees 173 files, the other sees 19,563 — because node_modules/ is 360 MB of somebody else’s code and dist/ is a build artifact I regenerate on every deploy. Searching those isn’t thoroughness, it’s noise. A grep -r for a config key returns your one real hit buried under two hundred matches from a transitive dependency, and you learn to stop trusting the output.

The speed follows from the same fact. Skipping 19,390 files you didn’t want is a bigger optimization than any amount of clever matching.

The Flag That Made It an Audit

Search tools find what’s there. Audits need what’s missing, and that’s a genuinely awkward thing to ask most tools for:

$ rg --files-without-match 'linkedinText' -g '*.md' src/data/blog | wc -l
      28

That’s the finding from Monday’s post, reproduced in one line. --files-without-match inverts the question from “which files contain this” to “which files don’t” — and “which files are missing the thing the docs say is required” is the shape of nearly every hygiene problem I’ve ever had.

The broken links were the same idea with a regex. The site routes posts at /posts/<category>/<slug>, and 39 links across 15 posts had been written with the category segment missing:

$ rg -n -g '*.md' '\]\(/posts/[^/)]+\)' src/data/blog

[^/)]+ means “one path segment, no slash” — a link with only a slug and no category. That command returned 39 hits in 15 files the day I ran it. Today it returns nothing, which is the entire point: once you can express the rule as a search, you can put it in a script and stop rediscovering it.

Four Flags Worth the Muscle Memory

  • -t md — search only Markdown. Types are built in for ~250 languages; rg --type-list shows them all. -t md beats --include="*.md" --include="*.mdx" --include="*.markdown" every time.
  • -g '*.yml' — glob when there’s no type for it, and -g '!vendor/**' to carve something out.
  • -r '$1' with -o — print a capture group instead of the line. rg -o --no-filename '^category: (.+)$' -r '$1' -g '*.md' src/data/blog | sort -u prints the nine category values actually in use, which is how you catch the tenth one somebody typo’d.
  • --stats — files searched, files matched, time spent. Cheap sanity check when a result count looks wrong.

The Gotcha

What rg skips by default is the best thing about it and the only thing that will genuinely confuse you: sometimes it “can’t find” a string you know is in the repo, because the file is gitignored, hidden, or binary.

Three escape hatches, in order of desperation:

rg --no-ignore 'pattern'    # ignore the ignore files
rg --hidden 'pattern'       # include dotfiles and dotdirs
rg -uuu 'pattern'           # all of the above, plus binaries — full grep parity

When a search comes back empty and you’d bet money it shouldn’t have, try -uuu before you question your regex.

Two Minutes to Switch

brew install ripgrep

Then don’t alias it to grep. The flags overlap but they aren’t the same tool, and aliasing means the muscle memory you build won’t survive your first ssh into a box that doesn’t have it. Just type rg and let the two-character savings be its own reward.

Monday’s post argued that a rule isn’t a rule until something enforces it. This is the front half of that: before you can enforce a convention, you have to be able to ask the repo a question about itself and trust the answer. That’s what a search tool that ignores your dependencies buys you — and it’s why the audit took an afternoon instead of a weekend.

Want the full argument for treating this stuff as an operational practice? Mise en Place: Repo Hygiene Is an Operational Practice is where the series starts.

Happy automating!