grep or ripgrep

2 min read

grep is on every Unix machine and handles one file at a time. ripgrep (rg) is built for a source tree: it walks directories in parallel, skips what .gitignore skips, and uses a regex engine that cannot go exponential. Both match lines against a pattern; the differences that matter are in what they search by default and how fast they get through a tree.

What differs

Concurrency
rg walks the tree and searches files on all cores at once, each thread with its own matcher. grep -r is one thread, in order. On a large repo the performance difference is substantial, more on fast storage. On one file, or on a pipe, they are close.
Ignore files
rg reads .gitignore, .ignore and .rgignore, skips hidden files and does not follow symlinks; --no-ignore, --hidden and -L turn each back on. grep searches everything unless told --exclude-dir by hand.
Binary files
rg stops at the first NUL byte and says so; grep searches on and prints "binary file matches". -a makes either treat the file as text; rg --binary searches binaries without printing their lines.
File types
rg -t go, rg -t js -t ts, rg -T minified: a built-in table of extensions, where grep needs --include='*.go' or a find.
Regex syntax
grep defaults to basic syntax, where +, ?, | and groups need a backslash; -E gives the extended one and -P Perl's. rg is extended by default, with \b for word boundaries and lazy quantifiers, and -P for look-around and backreferences, which its own engine leaves out to stay linear.
Portability
grep is POSIX and present on anything with a shell; a script that must run anywhere uses it. rg is a package to install, so it belongs to user editor, terminal, ..., not in /etc/cron.d.

Usage patterns

rg pattern                 # the tree, in parallel, ignore files honoured
rg -t go -w Lookup         # whole word, Go files only
rg -F 'a.literal[string]'  # no regex
rg -uu pattern             # hidden and ignored files too (-uuu: binaries as well)
grep -rn pattern dir       # the same tree, one thread, nothing skipped
grep -E 'a|b' file         # extended syntax; plain grep needs 'a\|b'

Both read a pipe the same way, so journalctl -f | grep x and journalctl -f | rg x are interchangeable; grep starts a few milliseconds faster, which matters only in a loop.