ripgrep Cheatsheet

⏱ 23 min read • TECH

ripgrep (rg) searches file contents recursively and filters the search space using ignore rules, hidden-file detection and binary detection. It is useful for finding code, configuration values and log records across a project, as well as for filtering a stream in the console.

This cheatsheet uses Bash on Linux. Examples were checked with ripgrep 15.2.0, including PCRE2 support. Most common options also exist in earlier releases; check rg --help for the installed version. Optional tools such as jq, gzip, sed and find are identified where used.

Related: grep fundamentals and Bash recipes, sed editing, and AWK text processing.

Quick Start

rg --version
rg -h              # Short option summary
# rg --help        # Full explanations

# Search the current directory recursively
rg -n -F 'TODO' .

# Search selected paths, rather than the whole working directory
rg -n 'timeout' ./src ./config

# Search one file
rg -n 'ERROR' app.log

# Filter standard input
printf 'alpha\nbeta\nalphabet\n' | rg 'alpha'
# alpha
# alphabet

With no explicit paths, rg normally searches the working directory when stdin is a terminal; when stdin is a pipe or redirected file, it searches that input instead. Give . explicitly when a directory search is intended regardless of stdin. A pattern is a regex by default; use -F for literal text.

Interactive output can include colors, headings and line numbers. Redirected output has different defaults. Examples below explicitly set output options where exact formatting matters and assume no custom RIPGREP_CONFIG_PATH configuration. Use --no-config to disable that configuration for a reproducible command.

A Reusable Log Fixture

Save as app.log:

INFO service started
WARN retry user=alice
ERROR timeout user=bob code=504
INFO user=alice request_id=req-17
ERROR disk full user=alice code=507
DEBUG healthcheck
error lowercase
rg -n 'ERROR' app.log
# 3:ERROR timeout user=bob code=504
# 5:ERROR disk full user=alice code=507

rg -i -n 'error' app.log
# Also includes 7:error lowercase

Defaults That Affect Results

DefaultConsequenceChange it with
Recursive directory searchNo -r is neededPass explicit files or limit depth
Case-sensitive regexCase and metacharacters matter-i, -S, -F
Ignore rules are respectedGenerated/vendor files may be absent--no-ignore, selective ignore flags
Hidden paths are skippedDotfiles are normally absent--hidden
Binary detection is activeNUL-containing content may be suppressed--binary or -a, with different effects
Encountered symlinks are skippedLinked directories are not traversed-L / --follow
Parallel searchFile order is not guaranteed--sort path
Human-oriented terminal outputOutput changes with the destination--no-heading, --color never, -n, -H

An explicitly supplied file can bypass automatic hidden/ignore filtering. A command-line glob can override ignore decisions too. Thus rg pattern ignored-file.txt is not equivalent to discovering that file by recursively searching its directory. A missing result is a reason to inspect the scope, not proof that the bytes are absent everywhere.

Common Options

OptionPurpose
-FFixed-string patterns instead of regex
-i, -s, -SIgnore case, force case-sensitive, smart case
-w, -xWhole-word / whole-line matches
-e PATTERN, -f FILEAdditional OR patterns / a pattern file
-vSelect lines not matching the patterns
-n, -NEnable / suppress line numbers
-H, -IEnable / suppress filename prefixes
--no-headingPut filename prefixes on result lines instead of separate headings
--columnAdd a 1-based byte column for the first match on a line
-oOutput only matched parts
-c, --count-matchesMatching lines / individual matches per file
--include-zeroInclude files with zero counts in count output
-l, --files-without-matchFilenames with matches / with zero matches
--filesList discoverable files without searching their contents
-g GLOB, --iglob GLOBInclude/exclude paths with a glob; second form ignores glob case
-t TYPE, -T TYPEInclude / exclude a file type
--hidden, --no-ignoreChange hidden-path and ignore-file filtering independently
-LFollow symlinks
-m NLimit matching lines per file
-A N, -B N, -C NAfter / before / surrounding context
-U, --multiline-dotallPermit multiline matches / let dot include newline
-P, --engine autoPCRE2 / automatic engine selection
-r TEXTReplace matches in printed output; no file editing
--passthruInclude nonmatching lines in output too
-0, --nullNUL after filenames in filename output
--null-dataUse NUL as the input/output record delimiter
-z, --search-zipSearch supported compressed content via external decompressors
--jsonStructured search events as JSON Lines
-qQuiet existence check
--line-bufferedFlush output promptly in a pipeline
--debug, --trace, --statsExplain search decisions / extra tracing / statistics
--no-configIgnore RIPGREP_CONFIG_PATH

Several short flags differ from grep. In rg, -r is replacement, -L follows links, -I hides filenames, and -z searches compressed files. See the comparison near the end before porting a grep command.

Literal Text, Case and Boundaries

Literal Strings versus Regex

printf 'a.b\naXb\n' | rg -N 'a.b'
# a.b
# aXb

printf 'a.b\naXb\n' | rg -NF 'a.b'
# a.b

# Quote shell data, use -F for literal content, and -e for a leading hyphen
needle='user=alice'
rg -nF -e "$needle" -- app.log
# Lines 2, 4 and 5

printf '%s\n' '-Ddebug' 'normal' | rg -NF -e '-Ddebug'
# -Ddebug

-- ends option parsing before paths. A path beginning with - can also be written as ./-notes.txt. As with grep, the special operand - means stdin; ./- refers to a file literally named -.

Smart Case

# Lowercase pattern: smart case searches case-insensitively
printf 'error\nERROR\nError\n' | rg -NS 'error'
# All three lines

# Uppercase in the pattern makes smart case case-sensitive
printf 'error\nERROR\nError\n' | rg -NS 'Error'
# Error

# Explicit overrides when case behavior must not depend on the pattern
rg -ni 'ERROR' app.log
rg -ns 'ERROR' app.log

Smart case is opt-in via -S unless enabled in configuration. It is based on the pattern, not the contents of the files.

Words and Whole Lines

printf 'cat\nconcatenate\ncat_1\ncat-like\n' | rg -Nw 'cat'
# cat
# cat-like

printf 'cat\ncat-like\n' | rg -NFx 'cat'
# cat

Word boundaries use Unicode-aware word semantics by default, including underscore. They do not mean “space-delimited token.” Whole-line literal matching with -Fx is useful for exact identifiers or line-based list membership.

Regular Expressions and PCRE2

Default Regex Reference

The default engine supports ERE-like syntax plus Unicode features; there is no need for a grep-style -E switch. In fact, rg -E means encoding, not extended regex.

PatternMeaning
. / .* / .+Any non-newline character / zero or more / one or more
^text, text$Start / end of a line
ERROR|WARNAlternatives
(ab)+, (?:ab)+Capturing / noncapturing group
[abc], [^abc]Character set / negation
[0-9]{3}, [0-9]{2,}Exact / minimum repetition
\bword\bWord-boundary assertions
\d, \w, \sUnicode-aware digit, word and whitespace classes
\p{Greek}Unicode property, here Greek script
(?i)errorInline case-insensitive flag
(?P<name>...)Named capture
.*?Lazy repetition; especially useful with multiline matching
printf 'req-17\nreq-001\nrequest-7\n' | rg -N '^req-[0-9]{2,3}$'
# req-17
# req-001

printf 'code=504\ncode=oops\n' | rg -No 'code=[0-9]+'
# code=504

# Default regex digit class includes non-ASCII Unicode digits
printf '123\n١٢٣\n' | rg -N '^\d+$'
# Both lines

printf '123\n١٢٣\n' | rg -N '^[0-9]+$'
# 123

The default engine does not support lookaround or backreferences. Unlike GNU grep BRE, \+ matches a literal plus rather than activating repetition. Choose the syntax deliberately instead of copying escapes between engines. Use ASCII classes such as [0-9] when the input contract is ASCII; --no-unicode changes regex semantics more broadly and is not just a performance flag.

Lookarounds and Backreferences with PCRE2

rg --pcre2-version

# Extract values without user=
rg -NoP '(?<=user=)[[:alnum:]_]+' app.log
# alice, bob, alice, alice (one per line)

printf 'cat cat\ncat dog\n' | rg -NP '^(\w+) \1$'
# cat cat

# Automatic engine choice uses PCRE2 when the pattern requires it
printf 'prefixpattern7\n' | rg -No --engine auto '(?<=prefix)pattern(?=[0-9])'
# pattern

PCRE2 is optional build functionality. Default-engine Unicode classes and PCRE2 classes are not interchangeable in every detail; test the actual engine for non-ASCII data. If the default engine rejects lookaround, -P is the relevant change, not -E.

OR, AND, NOT and Pattern Files

# OR with regex alternatives
rg -n 'ERROR|WARN' app.log

# OR with two literal strings
rg -nF -e 'ERROR' -e 'WARN' app.log

# AND on one line in any order; suppress prefixes before the second search
rg -NI -F 'ERROR' app.log | rg -NF 'user=alice'
# ERROR disk full user=alice code=507

# NOT: discard lines containing either literal term
rg -NvF -e 'DEBUG' -e 'healthcheck' app.log

# PCRE2 alternative for same-line AND while preserving rg's original prefixes
rg -nP '^(?=.*ERROR)(?=.*user=alice).*$' app.log
# 5:ERROR disk full user=alice code=507

Repeated -e means OR, not AND. A two-stage text pipeline must not let filenames or line-number prefixes satisfy the second condition accidentally; -NI removes those prefixes from the first stage. With PCRE2, lookaheads apply to the same line unless multiline/dotall flags deliberately change that scope.

A File of Search Terms

printf 'ERROR\nWARN\n' > terms.txt
rg -nF -f terms.txt app.log
# Lines 2, 3 and 5

Each pattern-file line is a pattern; comments are not interpreted. An empty file supplies no patterns, so it selects nothing. A blank pattern matches every line unless -x limits it to empty lines. A variable containing a newline similarly supplies multiple patterns; Bash variables cannot contain NUL bytes.

Compare Line-Based Lists

printf 'alice\nbob\ncarol\n' > users.txt
printf 'bob\ncarol\n' > allowed.txt
rg -NFxf allowed.txt users.txt
# bob
# carol
rg -NFvxf allowed.txt users.txt
# alice

These commands preserve input order and duplicates from users.txt. They are exact line-membership filters, not sorted/deduplicated mathematical set operations.

Ignore Rules, Hidden Paths and Missing Results

Controlled Ignore-Rule Example

Run in a scratch directory; this creates only the demo/ fixture:

mkdir -p demo/src demo/build
printf 'TODO source\n' > demo/src/app.txt
printf 'TODO generated\n' > demo/build/output.txt
printf 'TODO hidden\n' > demo/.settings
printf 'build/\n' > demo/.gitignore

# No Git metadata is needed for this fixture because of --no-require-git
rg --no-require-git --sort path -nF 'TODO' demo
# demo/src/app.txt:1:TODO source

# Add hidden paths, while retaining ignore rules
rg --no-require-git --hidden --sort path -nF 'TODO' demo
# Also finds demo/.settings; build/output.txt is still excluded

# Disable automatic ignore rules, but hidden paths are still excluded
rg --no-ignore --sort path -nF 'TODO' demo
# Finds build/output.txt and src/app.txt

# Include both ignored and hidden paths; deliberately exclude Git metadata
rg --hidden --no-ignore -g '!**/.git/**' --sort path -nF 'TODO' demo
# Finds all three TODO lines

By default, Git ignore rules are applied when a repository is detected. --no-require-git also applies them outside repositories, which is useful for source snapshots without .git metadata. .ignore and .rgignore are rg-aware filters independent of that Git-repository requirement.

Ignore Sources and Overrides

Source / flagUse
.gitignore, Git exclude/global rulesVCS-related filtering when applicable
.ignoreIgnore rules shared with compatible search tools
.rgignoreripgrep-specific ignore rules; takes precedence over .ignore and Git ignore rules
--ignore-file FILEAdd an explicit ignore file
--no-ignore-vcsDisable VCS ignore rules while retaining dot-ignore rules
--no-ignore-dotDisable .ignore / .rgignore; not hidden-file detection
--no-ignore-parentDo not load ignore rules from parent directories
--no-ignore-globalDisable global ignore sources
--no-ignoreDisable automatic ignore-file filtering; hidden/binary filtering remains
--hiddenAdd hidden paths; ignore rules still apply

Rules in more deeply nested ignore files can refine parent decisions, and ! negates a rule. A child cannot always be re-included if its parent directory was completely excluded from traversal; keep the parent traversable when writing exceptions. Command-line -g filters override ignore logic. Explicitly supplied --ignore-file rules can still apply with --no-ignore; use --no-ignore-files when intentionally disabling those too.

-u, -uu and -uuu

Shorthandripgrep 15.2 behavior
-u--no-ignore
-uu--no-ignore --hidden
-uuu--no-ignore --hidden --binary

In this version, -uuu enables binary search, not unrestricted binary-text output. To print matches from binary content as text, use -a explicitly. Symlink traversal still needs -L. Older versions have differed in the third -u behavior, so named flags make scripts easier to audit across installations.

Diagnose Scope before Changing the Pattern

# List the paths that discovery would consider
rg --files --sort path ./project

# Compare with a wider scope
rg --files --hidden --no-ignore -g '!**/.git/**' --sort path ./project

# Reasons for filtering and other decisions go to stderr
rg --debug -nF 'needle' ./project > matches.txt 2> search-debug.log
# --trace provides additional detail when --debug is insufficient

--files lists candidates without reading their contents, so it does not prove they contain a match or will survive content-based binary detection. Give a known skipped file directly to isolate discovery from matching: rg -nF 'needle' ./project/path/to/file.txt.

Globs, File Types and Search Scope

Globs Are Path Filters, Not Content Regexes

# Quote globs so Bash does not expand them
rg -nF 'TODO' ./project -g '*.java' -g '*.kt'

# Brace alternatives are handled by rg, not Bash (quotes prevent expansion)
rg -nF 'TODO' ./project -g '*.{js,ts,svelte}'

# Exclude tests and generated output
rg -nF 'TODO' ./project -g '!*.test.ts' -g '!**/build/**' -g '!**/vendor/**'

# Search a specific subtree: passing the path is often clearest
rg -nF 'TODO' ./project/src

# Case-insensitive filename selection, independent of content case
rg -nF 'TODO' ./project --iglob '*.md'

Positive globs select candidate paths; repeated positive file globs can include several suffixes. A leading ! excludes. If several command-line glob rules match, the later rule wins. Globs can override ignore exclusions, so adding -g can change more than the filename suffix filter. The !**/build/** form excludes build directories at any depth, including project/build when searching from its parent. A slash-containing pattern such as !build/** is path-relative and does not cover every nested build directory. Prefer an explicit subtree operand when that expresses the task directly; -g 'src' does not mean “all content under src.”

Built-In and Custom File Types

# Inspect available names and their glob definitions
rg --type-list | rg '^(java|kotlin|js):'

rg -n -t java -F 'TODO' ./project
rg -n -t java -t kotlin -F 'TODO' ./project
rg -n -T markdown -F 'TODO' ./project

# A custom type for this invocation only
rg --type-add 'jvm:*.{java,kt,kts}' -t jvm -nF 'TODO' ./project

Type filters are filename/glob definitions, not language parsers. -T excludes a type; it does not mean “invert content matches.” Custom definitions disappear after the process unless repeated in configuration.

Bound Depth and File Size

# Only direct children of project, not nested subdirectories
rg --max-depth 1 -nF 'TODO' ./project

# Skip large files; search statistics explain the work done
rg --max-filesize 2M --stats -nF 'TODO' ./project

# Follow encountered links when that scope is intentional
rg -L -nF 'TODO' ./project

A root directory has depth 0. Size/depth limits deliberately omit content; do not interpret a limited search as exhaustive. Following links can traverse outside the named tree or report loops/broken links.

Output, Context and Counting

Stable Text Output for Tools

rg --no-config --no-heading --color never -nH --sort path -F 'ERROR' app.log
# app.log:3:ERROR timeout user=bob code=504
# app.log:5:ERROR disk full user=alice code=507

rg --no-heading --color never -nH --column 'ERROR' app.log
# app.log:3:1:ERROR timeout user=bob code=504
# app.log:5:1:ERROR disk full user=alice code=507

--column reports byte positions, not display columns for tabs or wide Unicode characters. Filename order is not guaranteed without sorting; --sort path orders paths and disables parallel search. Do not split human result lines on : to recover filenames: filenames can contain colons/newlines. Use NUL-delimited filename output or JSON instead.

Context around Matches

rg -n -C 1 'ERROR' app.log
rg -n -B 2 -A 3 'ERROR' app.log

seq 1 9 | rg -n -C 1 --context-separator='====' '^[26]$'
# 1-1
# 2:2
# 3-3
# ====
# 5-5
# 6:6
# 7-7

Context lines are prefixed differently from matches, and overlapping windows merge. --no-context-separator removes separators between disconnected groups. Context output includes nonmatching data, which matters when piping it into a second filter.

Lines, Matches and Zero Counts

printf 'ERROR ERROR\nOK\nERROR\n' > counts.log
rg -c 'ERROR' counts.log
# 2
rg --count-matches 'ERROR' counts.log
# 3
rg -o 'ERROR' counts.log
# ERROR, ERROR, ERROR (one per line)

# Include zero counts explicitly; this still exits 1 if no match exists
rg -c --include-zero 'MISSING' counts.log
# 0

By default, rg omits a file with zero matches from count output. -c counts matching lines, whereas --count-matches counts individual matches. Combining -c with -o also counts matches, a difference from assuming every -c invocation counts lines. Counts are per file, not a grand total. -m N limits matching lines per file; multiline mode counts one multiline match once toward that limit.

Filenames with and without Matches

rg -l -F 'ERROR' ./logs
rg --files-without-match -F 'ERROR' ./logs

# Keep filenames containing whitespace/newlines intact
while IFS= read -r -d '' path; do
  printf 'matched file: %q\n' "$path"
done < <(rg -l0 -F 'ERROR' ./logs)

--files-without-match means zero matches in the file; -vl instead finds files with at least one nonmatching line. These differ for mixed-content files. In rg’s filename-without-match mode, printing a selected filename counts as a successful result, unlike relying on GNU grep -L’s status semantics. The reporting loop above does not propagate process-substitution errors; see the scripting recipe for explicit status handling.

Multiline Text, CRLF and NUL Records

Match across Lines

printf 'BEGIN\nalpha\nbeta\nEND\n' > block.txt

# -U allows the explicit newline in the pattern
rg -UNo 'BEGIN\nalpha' block.txt
# BEGIN
# alpha

# -U alone does not make dot include newline
rg -UNo --multiline-dotall 'BEGIN.*?END' block.txt
# BEGIN
# alpha
# beta
# END

# Inline dotall expresses the same intent
rg -UNo '(?s)BEGIN.*?END' block.txt

-U permits newline-spanning matches; --multiline-dotall or (?s) changes dot behavior. Neither is the same as merely adding context. Lazy .*? stops at the next suitable END marker, but does not balance nested blocks. A multiline search may need to hold a whole file in memory; use streaming logic when that is inappropriate.

CRLF and Encoding

printf 'ERROR\r\n' > windows.log
rg --crlf -n '^ERROR$' windows.log
# Matches despite the CRLF terminator

# Explicit decoding for a known legacy encoding
rg --encoding windows-1252 -n 'café' legacy.txt

The encoding name describes the input file. rg’s automatic encoding mode recognizes BOM-marked UTF-16; it is not a general detector for arbitrary legacy encodings. --crlf changes line-boundary recognition; it does not rewrite line endings. Regex Unicode behavior and file decoding are separate decisions.

NUL-Delimited Data versus NUL-Terminated Filenames

# NUL-separated input/output records; --null-data implies text mode
printf 'alpha\0beta\0' | rg --null-data -NF 'beta' | od -An -tx1
# 62 65 74 61 00

# Candidate filenames with a NUL separator, not newline
rg --files -0 ./project > candidate-files.nul

-0 terminates printed filenames with NUL. --null-data changes the record delimiter for searching content. rg -z is a different option entirely: compressed-file search.

Replacement Previews and Extraction

Replace Matches in the Printed Output

printf 'user=alice code=504\n' |
  rg -N 'user=(\w+)' -r 'account=$1'
# account=alice code=504

# Named group; -o prints just the transformed matched part
printf 'user=alice code=504\n' |
  rg -No 'user=(?P<name>\w+)' -r '${name}'
# alice

# Braces separate the group name from immediately following digits/text
printf 'PORT=3000\n' | rg -N '^(PORT=)[0-9]+$' -r '${1}8080'
# PORT=8080

# $$ represents a literal dollar in the replacement syntax
printf 'amount=42\n' | rg -N 'amount=([0-9]+)' -r '$$${1}'
# $42

Single quotes keep Bash from expanding $1 or ${name} before rg sees them. rg replacement references use $1/${name}, unlike sed’s \1; $0 refers to the full match and $$ gives a literal dollar. There is no in-place editing flag: -r changes output only.

Keep Unmatched Lines in a Complete Output Preview

Input service.env:

HOST=localhost
PORT=3000
MODE=dev
rg --no-config --color never --no-heading -NI --passthru \
  '^(PORT=)[0-9]+$' -r '${1}8080' service.env > service.updated.env
diff -u service.env service.updated.env
# Only PORT changes; diff exits 1 when it finds a difference

Without --passthru, nonmatching lines would be omitted. Even with passthrough, rg’s search status is 1 when no actual match exists, despite printing the unchanged lines. Save to a different path and handle that normal status if wrapping the transformation in a strict script. Shell redirection to the input path would truncate it before the search starts. For actual reviewed file edits, use an editor or the sed recipes.

Practical Project and Console Recipes

Locate Configuration References and Definition Shapes

rg -nF 'DATABASE_URL' ./project --hidden -g '!**/.git/**'
rg -n '(^|[[:space:]])timeout[[:space:]]*=' ./project -g '*.conf'

# A deliberately simple function-declaration shape, not a language parser
rg -n '^\s*(export\s+)?(async\s+)?function\s+\w+' ./project -g '*.{js,ts}'

# Include just one otherwise ignored file by naming it explicitly
rg -nF 'timeout' ./project/build/generated.conf

Hidden configuration files may still be ignored by rules even with --hidden. Regex-shaped definitions miss language constructs that use different syntax; use a syntax-aware tool when structural accuracy is required.

Find TODOs and Rank Repeated Values

rg --no-heading --color never -nH 'TODO|FIXME|HACK' ./project -g '!**/vendor/**'

# Extract user values, then count/sort with standard console tools
rg -No 'user=(\w+)' -r '$1' app.log |
  LC_ALL=C sort | uniq -c | sort -nr
# alice has count 3; bob has count 1

The pipeline extracts the captured value, not the user= key. sort changes order and uniq -c counts adjacent equal values after sorting. For filenames, do not use newline-delimited text pipelines unless the pathname contract excludes newlines.

Live Logs

# Runs until interrupted; tail starts at newly appended records
tail -n 0 -F app.log | rg --line-buffered 'ERROR|WARN'

# Every intermediate filter must flush promptly too
tail -n 0 -F app.log |
  rg --line-buffered -F 'ERROR' |
  rg --line-buffered -vF 'healthcheck'

Line buffering controls rg’s output, not whether the producing application flushes. tail -F is responsible for following file rotation; rg filters the stream it receives.

Compressed Files

# Requires gzip for this .gz fixture
printf 'INFO ready\nERROR archived\n' | gzip > archive.log.gz
rg -z -nF 'ERROR' archive.log.gz
# 2:ERROR archived

# Or explicitly decompress into stdin
gzip -cd -- archive.log.gz | rg -nF 'ERROR'
# 2:ERROR archived

rg uses external decompression programs for supported compressed formats. Compressed archives with multiple members/files are not a general archive-filesystem search interface; extract or enumerate entries with an archive-aware tool when needed. Check decompressor failures as well as match results in scripts.

Binary Content and Byte Offsets

printf 'header\0ERROR payload\n' > payload.bin

# Treat as text deliberately; inspect bytes rather than render binary control data
rg -a -NF 'ERROR' payload.bin | od -An -tx1

# Byte offset of an extracted match
printf 'xx ERROR\n' | rg -Nob 'ERROR'
# 3:ERROR

--binary allows detected binary files to be searched during traversal and may stop with a binary-match diagnostic. -a disables binary detection so text matches can be printed, including control bytes. Binary detection is heuristic, not a file-format decoder. A recursive search and an explicitly named binary file can therefore behave differently.

JSON Output for Automation

Use JSON Lines instead of parsing human-oriented path:line:column:text output:

rg --json -F 'ERROR' app.log > matches.jsonl

# jq example for this UTF-8 fixture: one TSV record per matching line
jq -r 'select(.type == "match") |
  [.data.path.text, .data.line_number, (.data.lines.text | rtrimstr("\n"))] |
  @tsv' matches.jsonl
# app.log<TAB>3<TAB>ERROR timeout user=bob code=504
# app.log<TAB>5<TAB>ERROR disk full user=alice code=507

Events include begin, match, context, end and summary. Match events expose submatches with byte ranges, so one line can contain multiple individual matches. Non-UTF-8 paths/content use base64 bytes fields instead of text; a general consumer must handle both. The jq recipe above intentionally assumes UTF-8 text. JSON carries structure but not deterministic file ordering unless ordering is also requested.

--json is a search-output mode: it is incompatible with --files, filename-only modes and count modes. Human-output replacement/heading/color options do not redefine its schema. Use the structured event data for machine consumption.

Reliable Bash Scripts

Match, No Match and Error

StatusMeaning for a normal search
0A match/result was found and no error occurred, with the quiet-mode exception
1No match/result, without an error
2Invalid regex, unreadable input or another search error

-q can return 0 once a match is found even if an error occurred; it is not a complete scan guarantee. Output modes such as --files and --files-without-match define their own selected result, so do not assume that their status means a content match.

if rg -qF 'ERROR' app.log; then
  printf '%s\n' 'ERROR exists'
else
  status=$?
  case "$status" in
    1) printf '%s\n' 'No ERROR records' ;;
    *) printf 'Search error: %s\n' "$status" >&2 ;;
  esac
fi

For ordinary text files where reading all input is required, omit early-exit flags and discard output instead:

if rg -F 'ERROR' app.log > /dev/null; then
  printf '%s\n' 'Match found and scan completed'
else
  status=$?
  printf 'Scan status: %s\n' "$status"
fi

Capture $? in the else branch directly. With if ! rg ..., $? inside the branch is the status of the negation, not rg’s original result. --no-messages hides some file diagnostics; it does not turn a failed read into success.

Check a Filename Search before Using Its Results

if rg -l0 -F 'ERROR' ./logs > matched-files.nul; then
  while IFS= read -r -d '' path; do
    printf 'matched file: %q\n' "$path"
  done < matched-files.nul
else
  status=$?
  case "$status" in
    1) printf '%s\n' 'No matching files' ;;
    *) printf 'Search failed: %s\n' "$status" >&2 ;;
  esac
fi

Keep the filename list outside the tree being searched. -l can stop at the first match in each file, so this checks the filename-search operation rather than proving every byte of every selected file was read.

pipefail and No-Match Status

(
  # A subshell keeps these diagnostic settings local
  set +e
  set +o pipefail
  printf 'INFO ready\n' | rg -F 'ERROR'
  statuses=("${PIPESTATUS[@]}")
  printf 'producer=%s rg=%s\n' "${statuses[0]}" "${statuses[1]}"
)
# producer=0 rg=1

A no-match status can stop a bare command under set -e; use if or explicit status handling where it is expected. With pipefail, a producer can receive SIGPIPE after an early consumer such as rg -q, rg -m or head closes the pipe, making the pipeline fail even when a match existed. Capture PIPESTATUS immediately, before another command overwrites it. Do not hide every failure with || true when errors matter.

Configuration and Repeatable Commands

A Local Configuration File

Save as ripgreprc:

# One argument per line; do not add surrounding shell quotes
--smart-case
--glob=!**/vendor/**
# Use this config for one command; no home-directory file is required
RIPGREP_CONFIG_PATH=./ripgreprc rg -n 'error' app.log
# Finds lowercase, uppercase and mixed-case error text

# Ignore the environment-specified config and state the needed case behavior
RIPGREP_CONFIG_PATH=./ripgreprc rg --no-config -ns 'ERROR' app.log
# Only the uppercase ERROR lines

rg reads the path from RIPGREP_CONFIG_PATH; it does not automatically discover a project .ripgreprc. Each nonempty, noncomment config line is one argument. Do not add shell quotes around arguments or expect variable expansion. Use --option=value or put the option and value on separate lines. CLI arguments are applied after config arguments; use --no-config when ambient defaults must not affect a script.

Inspect Performance without Changing Meaning Blindly

rg --no-config --stats -nF 'TODO' ./project
rg --no-config -j 1 -nF 'TODO' ./project
rg --no-config --sort path -nF 'TODO' ./project

Restricting the intended scope, using literal search where appropriate, and avoiding unnecessary output usually matter more than tuning thread counts. Sorting disables parallelism. Skipping hidden/ignored/binary files can make rg search less data than a broad grep invocation; compare equivalent scopes before attributing a timing difference entirely to the engine.

grep versus rg: Translate Intent, Not Flags

IntentGNU grepripgrep
Search a directory recursivelygrep -r PATTERN dirrg PATTERN dir
Literal stringgrep -F TEXT filerg -F TEXT file
Extended-style regexgrep -E REGEX filerg REGEX file (different engine/syntax details)
Filename without a matchgrep -L PATTERN dir/*rg --files-without-match PATTERN dir
Follow encountered linksgrep -R PATTERN dirrg -L PATTERN dir
Hide filename prefixesgrep -h PATTERN files...rg -I PATTERN files...
Treat binary as textgrep -a PATTERN filerg -a PATTERN file
NUL-terminated filenamesgrep -lZ PATTERN files...rg -l0 PATTERN paths...
NUL-delimited contentgrep -z PATTERN filerg --null-data PATTERN file
Search compressed contentzgrep PATTERN file.gzrg -z PATTERN file.gz
Replace printed matchesUse sed/AWK after greprg PATTERN -r REPLACEMENT file

GNU grep is a useful baseline for standard shell filtering; rg adds project-oriented discovery, file types, replacement output and structured results. They have different default scopes and regex engines. Use the grep cheatsheet for its own semantics rather than treating either tool as an exact flag-compatible replacement.

Troubleshooting

SymptomCheck
A known file is missing--files, --debug, hidden/ignore filters, explicit path
.gitignore seems inactive in an exported directoryRepository detection; try --no-require-git
--hidden still misses an ignored fileHidden and ignore rules are separate; choose the intended ignore override
A glob behaves like a shell wildcard unexpectedlyQuote -g arguments; remember globs filter paths, not contents
-E does not enable groupingrg -E selects encoding; default regex already supports grouping
Lookbehind/backreference is rejectedCheck PCRE2 support and use -P or --engine auto
Unicode digits match more than expectedUse an explicit ASCII [0-9] contract if appropriate
-L follows links instead of listing nonmatchesUse --files-without-match
-r did not edit the fileReplacement affects output only; use a file-editing tool
Zero counts are absentAdd --include-zero; status can still be 1
A multiline dot misses newlineCombine -U with dotall behavior
CRLF anchors miss expected textUse --crlf for the input’s line endings
Script output order changesAdd --sort path when deterministic path order matters
A file list breaks on spaces/newlinesUse -0 plus a NUL-aware consumer
A no-match command stops a scriptHandle status 1 independently from status 2
Printed output contains unwanted color/headingsSet --color never --no-heading and explicit prefix flags
Behavior changes on another machineInspect version, config, optional PCRE2, ignore sources and decoding

Resources for Further Learning

Comments

© 2025 CodeByMe.de | Engineering