Skip to content

ripgrep (rg) - Street-Level Ops

Real-world workflows for searching logs, configs, and code at speed.

Searching Logs for Errors

# Find all errors in application logs
rg "ERROR|FATAL|CRITICAL" /var/log/app/

# Show 3 lines of context around each match
rg -C 3 "OOMKilled" /var/log/syslog

# Output:
# /var/log/syslog-234: Mar 15 02:14:01 web-1 kernel: Memory cgroup out of memory
# /var/log/syslog-235: Mar 15 02:14:01 web-1 kernel: oom-kill:constraint=...
# /var/log/syslog-236: Mar 15 02:14:01 web-1 kernel: Killed process 12345 (java)
# /var/log/syslog-237: Mar 15 02:14:01 web-1 kernel: OOMKilled container api-svc
# /var/log/syslog-238: Mar 15 02:14:02 web-1 systemd: api-svc.service: main process exited
# /var/log/syslog-239: Mar 15 02:14:02 web-1 systemd: api-svc.service: restarting

# Search compressed logs (rg cannot read .gz natively — pipe through zcat)
zcat /var/log/syslog.1.gz | rg "connection refused"

# Count error occurrences per file
rg -c "ERROR" /var/log/app/

# Output:
# /var/log/app/api.log:142
# /var/log/app/worker.log:23
# /var/log/app/scheduler.log:3

Under the hood: rg is fast because it uses memory-mapped I/O, parallel directory traversal, and the Rust regex crate with a DFA engine. On a cold cache it beats grep -r by 2-10x; on a warm cache the gap narrows but rg still wins due to automatic .gitignore filtering and smarter directory walking.

Finding Secrets and Sensitive Data

# Find potential hardcoded credentials
rg -i "(password|secret|api.?key|token)\s*[:=]" --type-not md

# Find AWS access keys
rg "AKIA[0-9A-Z]{16}" .

# Find private keys
rg -l "BEGIN (RSA |EC |DSA |)PRIVATE KEY"

# Find hardcoded IP addresses in YAML
rg -P '\b\d{1,3}(\.\d{1,3}){3}\b' --type yaml

# Output:
# devops/helm/values-prod.yaml:  dbHost: 10.0.1.50
# devops/k8s/configmap.yaml:     redis: 10.0.2.30

Code Search Patterns

# Find all TODO/FIXME comments
rg "TODO|FIXME|HACK|XXX" --type py --type sh

# Find function definitions in Python
rg "def \w+" --type py -o | sort | uniq -c | sort -rn | head -10

# Find all imports of a specific module
rg "^(import|from) requests" --type py

# Find where a Kubernetes resource is defined
rg "kind: Deployment" --type yaml -l

# Output:
# devops/k8s/api-deployment.yaml
# devops/k8s/worker-deployment.yaml
# devops/helm/grokdevops/templates/deployment.yaml

# Word-boundary search (match "port" but not "export" or "report")
rg -w "port" --type yaml
# Find all Helm value references
rg "{{ \.Values\." --type yaml

# Find all Terraform resource definitions
rg "^resource " --type tf

# Find environment variable usage
rg "ENV |env:" --type docker --type yaml

# Find all listen/port configurations
rg -i "listen|port" --type conf --type yaml --type tf

# Find all references to a specific service
rg "api-service" --type yaml --type tf -l

File Filtering

# Search only Python files
rg -t py "import os"

# Search everything except test files
rg -g '!*test*' -g '!*spec*' "database"

# Search only Helm templates
rg -g 'templates/*.yaml' "{{ .Values"

# Exclude generated and vendor directories
rg -g '!vendor/' -g '!node_modules/' -g '!.terraform/' "config"

# Search only files tracked by git
rg --no-ignore-vcs "deprecated"

Gotcha: By default rg respects .gitignore, .ignore, and .rgignore files. This means searches inside node_modules/, vendor/, or .terraform/ return nothing unless you add --no-ignore. If a search returns no results but you know the pattern exists, try --no-ignore first before debugging the regex.

Replacement Preview

# Preview a replacement (does NOT modify files)
rg "old_function_name" --replace "new_function_name"

# Show what files would be affected
rg -l "old_api_url" --type py

# Actually perform the replacement (rg finds files, sed does the edit)
rg -l "old_api_url" --type py | xargs sed -i 's/old_api_url/new_api_url/g'

# Verify the replacement
rg "old_api_url" --type py  # Should return nothing
rg "new_api_url" --type py  # Should show the changes

Combining with Other Tools

# rg + fzf: interactive search results
rg --color=always "TODO" | fzf --ansi

# rg + jq: search JSON files then process
rg -l "error" --type json | xargs -I{} jq '.errors' {}

# rg + wc: count total matches across all files
rg -c "WARNING" /var/log/ | awk -F: '{sum+=$2} END {print sum}'

# rg + sort: find most common error messages
rg -o "Error: .*" /var/log/app/ | sort | uniq -c | sort -rn | head -10

# Output:
#   142 Error: connection timeout
#    87 Error: database unavailable
#    23 Error: invalid request format

Advanced Regex

# PCRE2 mode for lookaheads and backreferences
rg -P "timeout=(\d+)" --only-matching /etc/

# Multiline search (span across lines)
rg -U "def main.*\n.*return" --type py

# Match only the captured group
rg -P -o "version:\s*['\"](.+?)['\"]" --type yaml

# Case-insensitive search
rg -i "error" /var/log/syslog

# Smart case (case-insensitive unless pattern has uppercase)
rg -S "Error"  # Case-sensitive because "Error" has uppercase
rg -S "error"  # Case-insensitive because all lowercase

One-liner: Find the 10 most common log messages: rg -oN "ERROR: .*" /var/log/app/ | sort | uniq -c | sort -rn | head -10 — the -N suppresses line numbers for cleaner uniq grouping.

Remember: Smart case mnemonic: uppercase in pattern = case-sensitive, all lowercase = case-insensitive. Same as Vim's \C / smartcase behavior. Saves typing -i on most searches.

Configuration for DevOps

# Set up a global config file
export RIPGREP_CONFIG_PATH=~/.ripgreprc

cat > ~/.ripgreprc << 'EOF'
--smart-case
--glob=!.git/
--glob=!node_modules/
--glob=!.terraform/
--glob=!__pycache__/
--max-columns=200
--max-columns-preview
EOF