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
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.
grepandagare effectively single-threaded for a single search. - Memory-mapped files. Where it helps,
ripgrepmemory-maps files so the OS handles paging instead ofread()syscalls in a loop. - A fast regex engine. It uses Rust's
regexcrate, 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,ripgrepsimply 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.rgignorein 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 --hiddenalso searches hidden filesrg --no-ignorestops respecting.gitignorerg -uuuis the "search everything" mode: it is shorthand that disables ignore files, searches hidden files, and searches binary files, makingrgbehave likegrep -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
| Property | grep | ripgrep (rg) | ag |
|---|---|---|---|
| Typical speed on a large repo | Slowest | Fastest | Fast |
| On every system by default | Yes | No (install) | No (install) |
Respects .gitignore | No | Yes (default) | Yes (default) |
| Skips hidden and binary files | No | Yes (default) | Yes (default) |
| Recursive by default | No (needs -r) | Yes | Yes |
| Parallel across CPU cores | No | Yes | No |
| Regex engine | BRE / ERE, PCRE via -P (GNU only) | Rust regex; PCRE2 via --pcre2 | PCRE |
| Multiline search | No (line-oriented) | Yes (-U / --multiline) | Limited |
| Replace preview | No | Yes (-r replacement text with capture groups) | No |
| Line numbers and color by default | No | Yes | Yes |
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.
grepdefaults to BRE (basic regex), where+,?,|,(), and{}are literal unless backslash-escaped.grep -Eswitches to ERE (extended regex) where those metacharacters work bare.grep -Penables PCRE (lookarounds,\d,\w, non-greedy*?, named groups), but only on the GNU build; macOS BSD grep has no-P.ripgrepuses Rust's ownregexcrate 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--pcre2andrgswitches to the PCRE2 engine.aguses 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:
grep -rn ':pattern' :search_pathThen with ripgrep, which recurses, numbers lines, and colors output with no flags:
rg ':pattern' :search_pathThe 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:
rg -uuu ':pattern' :search_pathGoing the other direction, to make grep skip the directories ripgrep ignores for free, you spell them out by hand with --exclude-dir (GNU grep):
grep -rn --exclude-dir=node_modules --exclude-dir=.git ':pattern' :search_pathThat 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
rgis installed.grepis 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 500filters a stream.ripgrepreads stdin too, but for quick pipeline filteringgrepis the reflex and it is always there. - No extra install allowed. Production servers, locked-down build agents, customer machines: if you cannot install software,
grepis 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
rgwas built for. On a big monorepo it can be an order of magnitude faster thangrep -r, partly from parallelism and partly from skipping ignored paths. - Anything where sane defaults matter. Recursive, line numbers, color,
.gitignore-aware:rg patterndoes what you usually want with zero flags.grepneeds-rnand still searches junk. - You need a feature
greplacks. Multiline matching (-U), search-and-replace preview (-rwith capture groups), type filters (rg -t js), or PCRE2 lookaround (--pcre2). - Editor and tool integration.
ripgrepis the search backend inside VS Code, many Neovim setups, and tools likefzf. 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.
agis still a perfectly functional fast-grep, and if your fingers already typeag patternand it works, there is no crisis. But it is slower thanripgrep, less actively maintained, and offers nothingripgrepdoes not. For a new setup, installripgrepinstead.ackis 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:
sudo apt install ripgrepThe 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
- grep Cheat Sheet: the full grep reference covering flags, regex flavors, context lines, and the macOS BSD divergences
- grep recursive search: the
-r/-Rflag in depth, with the symlink and exclude patterns - grep exclude files and directories:
--exclude,--exclude-dir, and thefind | xargsfallback on BSD - Find files containing text: when to combine
findwith a content search instead of going recursive - External: GNU grep manual, ripgrep on GitHub, the_silver_searcher on GitHub





