rg — Behavioral Traps

Behavioral Traps

rg respects .gitignore — grep does not

rg skips files matched by .gitignore, .rgignore, and .ignore by default. This means files in node_modules/, build/, or target/ are invisible.

WRONG — wondering why rg finds nothing in a gitignored directory:

rg 'pattern' build/

CORRECT — use --no-ignore to search everything:

rg --no-ignore 'pattern' build/

Layer precedence: .gitignore < .ignore < .rgignore. All three are respected by default.


rg skips binary files by default — grep does not

rg detects binary files (by checking for NUL bytes) and skips them silently. grep processes them and produces garbled output.

WRONG — expecting to find matches in a binary file:

rg 'magic' /usr/bin/

CORRECT — explicitly opt in to binary search:

rg --binary 'magic' /usr/bin/

For text extraction from binaries, rg -a (alias for --text) treats all files as text. --binary is gentler — it searches binaries but replaces NUL bytes with placeholders.


rg skips hidden files/directories by default

Files and directories starting with . are invisible to rg unless told otherwise.

WRONG — searching for aliases but missing ~/.bashrc:

rg 'alias' ~/

CORRECT — include hidden files:

rg --hidden 'alias' ~/

Combine with --no-ignore for exhaustive search: rg --hidden --no-ignore 'pattern' dir/.


No BRE mode — rg is always ERE-like (Rust regex)

grep defaults to BRE (Basic Regular Expressions) where +, ?, {, ( are literal unless escaped. rg always uses an ERE-like syntax where these are metacharacters.

WRONG — escaping + as you would in grep BRE:

rg '\d\+\.\d\+' /etc/os-release

CORRECT — + is already a quantifier in rg:

rg '\d+\.\d+' /etc/os-release

This is a common trap for grep veterans. In rg, + matches a literal + character. In grep BRE, \+ means "one or more."


PCRE2 must be explicitly requested with -P

Lookaheads, lookbehinds, backreferences, \K — none of these work without -P.

WRONG — using a lookbehind without -P:

rg '(?<=port=)\d+' /etc/services

This produces an error: regex parse error.

CORRECT — enable PCRE2:

rg -P '(?<=port=)\d+' /etc/services | head -5

Check PCRE2 availability: rg --pcre2-version. If it says "unavailable," your build was compiled without it.


--glob paths are relative to the search root, not cwd

Glob patterns match against the path relative to the search directory, not the absolute path or cwd.

WRONG — assuming glob matches against the full path:

rg -g '*/ROOT/pages/*.adoc' 'pattern' ~/atelier/_bibliotheca/domus-captures/

CORRECT — glob is relative to the search root:

rg -g '*.adoc' 'pattern' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/

For nested directory matching, use : -g '/pages//*.adoc'. The glob matches any number of directories.


Non-zero exit on no match — guard in scripts

rg exits with code 1 when no matches are found. In set -e scripts, this kills the script.

WRONG — unguarded rg in a strict script:

#!/bin/bash
set -e
count=$(rg -c 'pattern' file.txt)  # exits 1 if no matches — script dies

CORRECT — guard with || true or test exit code:

#!/bin/bash
set -e
count=$(rg -c 'pattern' file.txt || true)
# OR
if rg -q 'pattern' file.txt; then
  echo "found"
fi

Exit codes: 0 = matches found, 1 = no matches, 2 = error (bad regex, missing file).


Non-deterministic output order — use --sort path for reproducibility

rg searches files in parallel by default. Output order varies between runs.

WRONG — expecting stable output for diffs:

rg -l 'pattern' docs/ > /tmp/before.txt
# ... make changes ...
rg -l 'pattern' docs/ > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt  # false positives from reordering

CORRECT — force deterministic order:

rg --sort path -l 'pattern' docs/ > /tmp/before.txt
# ... make changes ...
rg --sort path -l 'pattern' docs/ > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt

--sort path disables parallelism (necessarily). For large codebases, accept the speed cost or sort the output: rg -l 'pattern' | sort.