TechEarl

grep vs ripgrep vs ag: Which Search Tool to Use

grep is on every system and searches exactly what you point it at. ripgrep (rg) is the fast Rust-based default for code search: it skips .gitignore'd, hidden, and binary files unless told otherwise. ag (the_silver_searcher) was the older fast-grep, now largely superseded by ripgrep. This breaks down speed, defaults, regex engines, and exactly when to reach for each one.

Ishan KarunaratneIshan Karunaratne⏱️ 14 min readUpdated
grep is universal and searches everything you point it at; ripgrep (rg) is the fast Rust default that skips .gitignore'd and binary files; ag is the older fast-grep now superseded. Compare speed, defaults, and regex engines.

grep, ripgrep (rg), and ag (the_silver_searcher) all answer the same question: "which lines in these files match this pattern". They differ in two things that matter day to day: how fast they are, and what they search by default.

grep is the universal one. It is POSIX, it ships on every Unix-like system, and it searches exactly what you point it at, nothing more and nothing less. ripgrep is the modern default for code search: a Rust tool built for speed that recurses by default and quietly skips files listed in .gitignore, hidden files, and binaries. ag was the tool that made fast recursive code search popular before ripgrep existed; today it is largely superseded. This page is the comparison I reach for when deciding which one to type.

Set your values

Try it with your own values

Set your OS, the directory to search, and the pattern. Every command below updates with your values.

The three tools

GNU grep is the baseline. It is part of every Linux distribution, every macOS install ships a BSD variant of it, and Windows can run it through WSL or Git Bash. It is line-oriented, supports basic and extended regex out of the box, PCRE through -P on the GNU build, and it does exactly what its arguments say. Recursive search is opt-in with -r. There is nothing to install, which is the whole point: a script that uses grep runs anywhere.

ripgrep (rg) is the fast one. Written in Rust by Andrew Gallant, it was built specifically to grep source trees quickly. It recurses by default, prints line numbers and color by default, and it respects your .gitignore so a search inside a repo skips node_modules, target, build artifacts, and anything else you have already told git to ignore. For interactive code search it is the tool I actually type.

ag (the_silver_searcher) predates ripgrep. It was written in C as a faster, .gitignore-aware alternative to ack, and for several years it was the fast-grep everyone reached for. It still works, and it still ships in package repositories, but ripgrep is faster and more actively maintained, so ag is now mostly legacy muscle memory.

ack deserves a mention as the original. ack is a Perl program from 2005 marketed as "a grep-like tool optimized for programmers": it skips version-control directories, knows file types, and has friendly defaults. It is slower than all three of the above because it is interpreted Perl, but it is the tool that established the idea of a "better grep for code". ag was the faster C answer to ack; ripgrep is the faster Rust answer to ag.

Why ripgrep is fast

ripgrep is not fast by accident. Several design choices stack up:

  • Parallelism. It searches multiple files concurrently across all your CPU cores. grep and ag are effectively single-threaded for a single search.
  • Memory-mapped files. Where it helps, ripgrep memory-maps files so the OS handles paging instead of read() syscalls in a loop.
  • A fast regex engine. It uses Rust's regex crate, a finite-automata engine with no catastrophic backtracking, plus SIMD-accelerated literal scanning to skip quickly to candidate lines.
  • It searches less. By skipping .gitignore'd paths, hidden files, and binary files by default, ripgrep simply never opens most of the files in a typical repo. The fastest search is the one you do not run.

That last point is the one people underestimate. A grep -r in a Node project walks every byte of node_modules; rg skips the whole directory because git already ignores it.

The big behavioral difference: what gets searched

This is the single most important thing to understand before switching tools.

grep searches everything you point it at. grep -r pattern . reads every file under the current directory, including .git, node_modules, minified bundles, and binary blobs. It is literal about its arguments.

ripgrep searches only what it thinks is relevant code. By default rg pattern skips:

  • files and directories matched by any .gitignore, .ignore, or .rgignore in scope
  • hidden files and directories (anything starting with a dot)
  • binary files

That is a great default for interactive code search and an occasional surprise when a file you expected to match is being ignored. The fix is filter flags:

  • rg --hidden also searches hidden files
  • rg --no-ignore stops respecting .gitignore
  • rg -uuu is the "search everything" mode: it is shorthand that disables ignore files, searches hidden files, and searches binary files, making rg behave like grep -r

ag sits in the middle: like ripgrep it respects .gitignore and skips hidden files by default, but it is slower and its ignore handling has more edge cases.

grep vs ripgrep vs ag compared

Propertygrepripgrep (rg)ag
Typical speed on a large repoSlowestFastestFast
On every system by defaultYesNo (install)No (install)
Respects .gitignoreNoYes (default)Yes (default)
Skips hidden and binary filesNoYes (default)Yes (default)
Recursive by defaultNo (needs -r)YesYes
Parallel across CPU coresNoYesNo
Regex engineBRE / ERE, PCRE via -P (GNU only)Rust regex; PCRE2 via --pcre2PCRE
Multiline searchNo (line-oriented)Yes (-U / --multiline)Limited
Replace previewNoYes (-r replacement text with capture groups)No
Line numbers and color by defaultNoYesYes

The shape of the table is the decision. grep trades speed and convenience for being everywhere. ripgrep wins on speed, defaults, and features but has to be installed. ag is a worse ripgrep kept alive by habit.

Regex engines

The pattern syntax differs between the three, and this trips people up when porting a pattern.

  • grep defaults to BRE (basic regex), where +, ?, |, (), and {} are literal unless backslash-escaped. grep -E switches to ERE (extended regex) where those metacharacters work bare. grep -P enables PCRE (lookarounds, \d, \w, non-greedy *?, named groups), but only on the GNU build; macOS BSD grep has no -P.
  • ripgrep uses Rust's own regex crate by default. It supports \d, \w, \b, named groups, and Unicode classes, but deliberately not backreferences or lookaround, because the finite-automata design guarantees linear-time matching. When you genuinely need lookaround or backreferences, pass --pcre2 and rg switches to the PCRE2 engine.
  • ag uses PCRE throughout, so lookaround and backreferences are available without a flag.

The practical takeaway: a pattern that uses lookahead works in grep -P, in rg --pcre2, and in ag unflagged, but not in plain rg. For most code searches, plain rg is enough and faster.

The equivalent search, side by side

The same recursive search for :pattern under :search_path, first with grep:

bash· Linux (GNU)
grep -rn ':pattern' :search_path

Then with ripgrep, which recurses, numbers lines, and colors output with no flags:

bash· Linux (GNU)
rg ':pattern' :search_path

The grep command reads every file under the path. The rg command skips anything in .gitignore, hidden files, and binaries. To make rg search everything the way grep -r does, add -uuu:

bash· Linux (GNU)
rg -uuu ':pattern' :search_path

Going the other direction, to make grep skip the directories ripgrep ignores for free, you spell them out by hand with --exclude-dir (GNU grep):

bash· Linux (GNU)
grep -rn --exclude-dir=node_modules --exclude-dir=.git ':pattern' :search_path

That verbosity is exactly what ripgrep exists to remove.

When to use grep

Reach for grep when portability and ubiquity matter more than speed:

  • Scripts that must run anywhere. A deploy hook, a CI step, an init script, or anything that runs on a minimal container image cannot assume rg is installed. grep is part of the base system everywhere.
  • POSIX portability. If the script has to run on Linux, macOS, BSD, and an Alpine container, the only safe assumption is grep (and even then, mind the GNU vs BSD flag differences covered in the grep cheat sheet).
  • Piping arbitrary stdin. journalctl -u nginx | grep 500 filters a stream. ripgrep reads stdin too, but for quick pipeline filtering grep is the reflex and it is always there.
  • No extra install allowed. Production servers, locked-down build agents, customer machines: if you cannot install software, grep is what you have.

If the task is automation or runs on a box you do not control, use grep.

When to use ripgrep

Reach for ripgrep for interactive work on code:

  • Searching a large repository. This is the case rg was built for. On a big monorepo it can be an order of magnitude faster than grep -r, partly from parallelism and partly from skipping ignored paths.
  • Anything where sane defaults matter. Recursive, line numbers, color, .gitignore-aware: rg pattern does what you usually want with zero flags. grep needs -rn and still searches junk.
  • You need a feature grep lacks. Multiline matching (-U), search-and-replace preview (-r with capture groups), type filters (rg -t js), or PCRE2 lookaround (--pcre2).
  • Editor and tool integration. ripgrep is the search backend inside VS Code, many Neovim setups, and tools like fzf. Using it directly at the terminal keeps your behavior consistent with your editor.

If you are sitting in a repo searching for a symbol, use rg.

When to use ag or ack

Honestly: mostly do not, on new work.

  • ag is still a perfectly functional fast-grep, and if your fingers already type ag pattern and it works, there is no crisis. But it is slower than ripgrep, less actively maintained, and offers nothing ripgrep does not. For a new setup, install ripgrep instead.
  • ack is worth keeping only where Perl is guaranteed and Rust binaries are not, or where a script already depends on its specific output format. It is the slowest of the four because it is interpreted.

The lineage is ack then ag then ripgrep, each faster than the last. Unless you have a concrete reason to stay on an older link in that chain, use the newest one.

Installing ripgrep

ripgrep is a single static binary with no runtime dependencies, so installation is quick:

bash· Linux (GNU)
sudo apt install ripgrep

The binary is rg regardless of platform. On older Debian or Ubuntu releases where the ripgrep package is missing, download the release binary from the project's GitHub page or use cargo install ripgrep. ag installs as the_silver_searcher (apt install silversearcher-ag, brew install the_silver_searcher).

Common mistakes

1. Expecting ripgrep to search .gitignore'd files. The most common surprise: you rg for a string you know is in dist/bundle.js, and rg returns nothing because dist/ is in .gitignore. This is by design. Add --no-ignore to search ignored paths, or -uuu to disable all filtering.

2. Expecting grep to skip node_modules automatically. The mirror-image mistake. grep -r pattern . in a JavaScript project crawls all of node_modules and takes forever. grep has no concept of .gitignore. You must pass --exclude-dir=node_modules yourself, every time.

3. Scripting around ripgrep, then deploying to a box without it. A CI script that calls rg works on your laptop and fails on a fresh build agent or a slim container with rg: command not found. For anything that runs on infrastructure you do not fully control, use grep, or have the script install ripgrep first and check it exists.

4. Porting a PCRE pattern to plain ripgrep. A pattern with lookahead or a backreference works in grep -P and ag but fails or behaves differently in plain rg, because the default Rust engine does not support those constructs. Add --pcre2 to rg for parity.

5. Forgetting hidden files are skipped. rg does not search dotfiles by default, so a search for a value in .env or .github/workflows/ returns nothing. Add --hidden.

6. Assuming rg -r means recursive. In grep, -r is recursive. In ripgrep, recursion is the default and -r is the replacement flag for search-and-replace. Reading grep muscle memory onto rg flags causes confusing results.

See also

FAQ

TagsgrepripgrepagCLILinuxmacOSCode Search
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

find walks the live filesystem every time; locate and plocate query a prebuilt updatedb database. Compare freshness, speed, permission-awareness, and filtering, plus the mlocate vs plocate split and the macOS mdfind alternative.

find vs locate vs mlocate: Which File Search Tool to Use

find walks the live filesystem every time it runs: always current, sometimes slow. locate queries a prebuilt database: instant, but stale until the next updatedb. This breaks down the locate family (mlocate, plocate, slocate), the macOS situation, and exactly when to reach for each one.

Build an LLM agent with tool use. The agentic loop, tool-call formats on Anthropic / OpenAI / Gemini, JavaScript and Python code, common failure modes.

How to Build an LLM Agent with Tool Use

Build an LLM agent with tool use: the agentic loop, the tool-call format on Anthropic, OpenAI, and Gemini, runnable code in JavaScript and Python, plus the common failure modes.

find -exec ... {} + batches arguments into one command (fast). find ... -exec ... {} \; forks per file (slow). xargs adds shell flexibility but needs -0 for safety. The decision matrix and performance comparison.

find -exec vs xargs: Which to Use (and the {} + Trick That Beats Both)

find -exec ... {} + and find -print0 | xargs -0 are roughly equivalent for batch operations on matched files. find -exec ... {} \; forks once per match and is much slower. The decision matrix: when -exec is enough, when xargs adds value, and the safety rules for filenames with spaces, newlines, and quotes.