rg — Structured Output
JSON Structured Output
rg’s --json flag emits one JSON object per line — newline-delimited JSON (NDJSON). Each object has a type field: begin (file start), match (a hit), end (file done), summary (final stats). The match type contains data with path, lines, line_number, submatches, and byte offsets.
cat <<'EOF' > /tmp/rg-json-test.adoc
= Test Page
:description: A test page
// rg/basics — Core search patterns
// Usage: include::partial$codex/rg/basics.adoc[]
// Last updated: 2026-04-11
// Status: POPULATED
== Pattern Search
.Basic pattern search against /etc/passwd
[source,bash]
rg 'root' /etc/passwd
Expected output: [source,text]
1:root:x:0:0::/root:/bin/bash
`rg` prints line numbers by default, highlights matches in color. No flags needed for the common case. --- .Case-insensitive search (-i) across docs/ [source,bash]
rg -i 'antora' ~/atelier/_bibliotheca/domus-captures/docs/
`-i` maps to `grep -i`. Searches recursively by default — no `-r` flag needed. --- .Word match (-w) — prevent partial matches [source,bash]
rg -w 'log' /etc/passwd
Without `-w`, `log` matches `syslog`, `dialogd`, `catalog`. With `-w`, only whole-word boundaries match. Equivalent to `grep -w` or wrapping the pattern in `\b...\b`. --- .Fixed string / literal search (-F) — bypass regex engine [source,bash]
rg -F '$$' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/
`-F` treats the pattern as a literal string. Essential when searching for regex metacharacters (`$`, `.`, `*`, `+`, `?`, `[`, `{`) without escaping each one.
---
.Invert match (-v) — show non-matching lines
[source,bash]
rg -v '^#' /etc/fstab
Prints every line that does NOT match the pattern. Here: all uncommented lines in fstab. --- .Count matches per file (-c) [source,bash]
rg -c 'include::' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/grep/
Expected output: [source,text]
basics.adoc:3 index.adoc:0 gotchas.adoc:2 pcre.adoc:2
Reports count per file. Files with 0 matches are shown too (unlike `grep -c` which omits them by default with `-r`). --- .First match only (-m 1) — early exit [source,bash]
rg -m 1 'nologin' /etc/passwd
Stops after the first match in each file. Useful for existence checks in large trees. --- .Multiline mode (-U) — match across line boundaries [source,bash]
cat <<'EOF' > /tmp/rg-multiline-test.txt function setup() { echo "init" } EOF rg -U 'function.\{[\s\S]?\}' /tmp/rg-multiline-test.txt
`-U` enables multiline mode. `[\s\S]*?` matches anything including newlines (non-greedy). Without `-U`, `.` never matches `\n` and patterns cannot span lines. --- .Quiet mode (-q) for scripting — exit code only [source,bash]
if rg -q 'PermitRootLogin' /etc/ssh/sshd_config; then echo "PermitRootLogin directive found" else echo "PermitRootLogin directive missing" fi
`-q` suppresses all output. Exit code 0 = match found, 1 = no match, 2 = error. Same semantics as `grep -q`. --- .Multiple patterns with -e — match any of several patterns [source,bash]
rg -e 'TODO' -e 'FIXME' -e 'HACK' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/partials/
Each `-e` adds an alternative pattern. Lines matching ANY pattern are printed. Equivalent to `grep -e 'A' -e 'B'` or `rg 'TODO|FIXME|HACK'` — but `-e` is cleaner when patterns contain regex metacharacters. EOF rg --json 'include::' /tmp/rg-json-test.adoc | head -5
Each line is a self-contained JSON object. The match type is what you usually want — it contains path.text, lines.text, line_number, and submatches (array of match positions). Pipe to jq to extract specific fields.
rg --json 'include::partial' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/rg/ | jq -r 'select(.type == "match") | .data.path.text' | sort -u
select(.type == "match") filters to only match objects. .data.path.text extracts the file path. sort -u deduplicates since each match line emits a separate object.
rg --json ':description:' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/rg/ | jq -r 'select(.type == "match") | "\(.data.path.text):\(.data.line_number): \(.data.lines.text | rtrimstr("\n"))"'
Builds a custom file:line: text format from JSON fields. .data.lines.text includes the trailing newline — rtrimstr("\n") strips it. This gives you the same output as normal rg, but you control the format precisely.
rg --json 'include::' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/rg/ | jq -r 'select(.type == "match") | .data.path.text' | sort | uniq -c | sort -rn
The JSON stream has one object per match, so counting path occurrences gives matches-per-file. sort -rn ranks by frequency. Equivalent to rg -c but with the full path and sortable output.
rg --json ':navtitle:' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/rg/ | jq -r 'select(.type == "match") | [(.data.path.text | split("/") | last), .data.line_number, (.data.lines.text | rtrimstr("\n") | ltrimstr(":navtitle: "))] | @csv'
@csv produces RFC 4180 CSV with proper quoting. split("/") | last extracts just the filename from the full path. Pipe to a file: > /tmp/navtitles.csv for import into any spreadsheet tool.
rg --json 'xref:' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/rg/index.adoc | python3 -c "
import sys, json
for line in sys.stdin:
obj = json.loads(line)
if obj['type'] == 'match':
for sub in obj['data']['submatches']:
print(f\" line {obj['data']['line_number']}: {sub['match']['text']}\")" 2>/dev/null || echo "No xref matches found"
When jq’s filtering gets unwieldy, switch to Python. The submatches array contains the exact matched text and byte offsets — useful for extracting multiple matches per line or building custom reports.
diff <(rg --json 'include::partial' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/rg/ | jq -r 'select(.type == "match") | .data.path.text' | sort -u) <(rg --json 'include::example' ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/pages/codex/rg/ | jq -r 'select(.type == "match") | .data.path.text' | sort -u) || true
Process substitution feeds two rg JSON pipelines into diff. Shows which files use partial includes vs example includes. The || true prevents a non-zero exit when differences exist — expected behavior, not an error.
rg --json '.' -t adoc ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/partials/codex/rg/ | jq -r 'select(.type == "summary") | "Files searched: \(.data.stats.searches_with_match)/\(.data.stats.searches) | Matches: \(.data.stats.matches) | Bytes: \(.data.stats.bytes_searched)"'
The summary type object (emitted once at the end) contains aggregate stats: total files searched, files with matches, total matches, bytes processed, and elapsed time. This is the machine-readable equivalent of --stats.