$linuxjunkies
>

Replace grep and find with ripgrep and fd

Replace grep and find with ripgrep and fd for faster searches, automatic .gitignore support, and safer scripting patterns—with key gotchas explained.

BeginnerUbuntuDebianFedoraArch8 min readUpdated June 7, 2026

Before you start

  • A terminal and a non-root user with sudo privileges
  • Basic familiarity with grep and find command syntax
  • Internet access or configured package repositories

ripgrep (rg) and fd are modern rewrites of grep and find that are faster, safer to script, and more ergonomic out of the box. ripgrep searches file contents using Rust's regex engine with SIMD acceleration; fd finds files and directories with sane defaults and a cleaner syntax. Neither is a complete drop-in replacement—they make different default choices—so this guide covers both the wins and the gotchas before you alias your muscle memory away.

Installation

Debian / Ubuntu

sudo apt install ripgrep fd-find

On Debian/Ubuntu, the fd binary is installed as fdfind to avoid a conflict with an unrelated fd package. Add a personal alias or symlink:

mkdir -p ~/.local/bin
ln -s "$(which fdfind)" ~/.local/bin/fd
# Make sure ~/.local/bin is in your PATH

Fedora / RHEL 9+ / Rocky

sudo dnf install ripgrep fd-find

Arch

sudo pacman -S ripgrep fd

Why They Are Faster

Speed comes from several concrete decisions, not just "Rust is fast":

  • ripgrep skips binary files automatically, respects .gitignore by default, and uses SIMD-accelerated literal scanning. On large codebases it routinely outperforms GNU grep by 2–10×.
  • fd parallelises directory traversal across CPU cores, also respects .gitignore and hidden-file rules by default, and avoids the overhead of GNU find's expression parser for common cases.
  • Both tools skip .git/ directories automatically, which matters enormously in repository trees where find . -name "*.rs" would otherwise crawl object storage.

Replacing Common grep Patterns

GNU grep needs -r and you must remember to exclude irrelevant directories manually. ripgrep is recursive by default:

# Old way
grep -r "TODO" . --include="*.py" --exclude-dir=.git

# New way
rg "TODO" --type py

The --type flag understands dozens of language aliases (rg --type-list shows them all).

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

# Literal string, no regex interpretation (equivalent to grep -F)
rg -F "192.168.1.1" /etc/hosts

Show only filenames

rg -l "import requests" --type py

Multiline and context lines

# 3 lines of context above and below each match
rg -C 3 "panic" src/

# Multiline mode: match patterns that span newlines
rg -U "fn main\(\)[\s\S]*?\{" src/main.rs

Search hidden files and ignored paths

ripgrep skips dotfiles and .gitignore entries by default. Override with flags:

# Include hidden files
rg --hidden "secret" ~

# Ignore all .gitignore rules and search everything
rg --no-ignore --hidden "TODO" .

Replacing Common find Patterns

Find files by name

# Old way
find . -name "*.conf" -type f

# New way (glob by default)
fd -e conf

# Or with a regex pattern
fd '\.conf$'

Find by file type

# Directories only
fd -t d "node_modules"

# Executable files
fd -t x

Find and execute a command

fd's -x (execute once per file) and -X (execute once with all results as arguments) replace find ... -exec and find ... | xargs:

# Compress every .log file (one process per file)
fd -e log -x gzip {}

# Run a single command with all results (equivalent to xargs)
fd -e txt -X wc -l

{} is the placeholder for the matched path. fd also exposes {/} (filename), {.} (path without extension), and {//} (parent directory).

Find files modified recently

# Files changed in the last 24 hours
fd --changed-within 24h

# Files changed before a date
fd --changed-before 2024-01-01

Depth limiting

# Maximum depth of 2 directories
fd -d 2 -e yaml /etc

Scripting Patterns and Gotchas

Scripting with ripgrep

When using rg inside scripts, force plain output so terminal colour codes don't corrupt pipelines, and treat the exit code explicitly. rg exits 0 on match, 1 on no match, 2 on error—the same as GNU grep.

#!/usr/bin/env bash
set -euo pipefail

if rg --quiet --no-heading "CRITICAL" /var/log/app.log; then
  echo "Critical entries found" >&2
  rg --no-heading --color=never "CRITICAL" /var/log/app.log
fi

Use --color=never or --no-heading any time you pipe or redirect. The --quiet flag exits immediately on the first match, which is fast for existence checks.

Scripting with fd

fd prints NUL-terminated filenames with --print0, making it safe for filenames that contain spaces or newlines:

#!/usr/bin/env bash
fd --print0 -e jpg | while IFS= read -r -d '' file; do
  echo "Processing: $file"
done

The .gitignore gotcha

Both tools respect .gitignore by default. This is usually what you want in a project, but it will silently omit files when you run them outside a git repo context or when searching directories where certain files are intentionally ignored (build artefacts, logs). Always add --no-ignore when you need exhaustive results in scripts:

fd --no-ignore --hidden -e log /var/log

Regex dialect differences

ripgrep uses Rust's regex crate, which does not support backreferences or lookaheads with arbitrary complexity. If you rely on PCRE features like (?<=...) lookbehind or backreferences, pass -P to enable the PCRE2 engine (requires ripgrep to be compiled with PCRE2 support, which most distro packages include):

rg -P "(?<=def )\w+" --type py

Verification

Confirm both tools are installed and working:

rg --version
fd --version   # or fdfind --version on Debian/Ubuntu

Expected output is a version string like ripgrep 14.x.x and fd 9.x.x (exact numbers vary by distro and release).

Run a quick functional test in any directory with a git repo:

rg "TODO" --stats .
fd -e md --max-results 5

The --stats flag on ripgrep prints how many files were searched, skipped, and matched—useful for confirming ignore rules are behaving as expected.

Troubleshooting

  • fd not found on Debian/Ubuntu: The binary is fdfind. Create the symlink shown in the installation section or add alias fd=fdfind to your shell rc file.
  • Expected files missing from results: The most common cause is an active .gitignore or a parent .ignore file. Run with --no-ignore --hidden to see everything, then narrow back down.
  • ripgrep skipping binary files: Use --text (-a) to force ripgrep to treat all files as text. Be aware this can produce garbled output on true binary files.
  • Regex not matching as expected: Test your pattern in isolation with rg -e 'pattern' <<< 'test string'. If you need PCRE2 features, confirm support with rg --pcre2-version.
  • Slow performance on network filesystems: Both tools parallelise aggressively. On NFS or FUSE mounts this can cause contention. Limit parallelism with rg -j 1 or fd -j 1.
tested on:Ubuntu 24.04Debian 12Fedora 40Arch rolling

Frequently asked questions

Does ripgrep fully replace grep in all situations?
No. ripgrep doesn't support searching a single piped stdin stream as naturally, lacks some POSIX grep options used in legacy scripts, and its default regex engine omits backreferences. Use grep for POSIX compliance in portable shell scripts and rg for interactive and project-level searches.
Why does fd miss files that find returns?
fd respects .gitignore and skips hidden files by default. Run fd --no-ignore --hidden to disable both behaviours and match find's exhaustive output.
Can I use ripgrep to search compressed log files?
Not natively. Decompress first with zcat or zstdcat and pipe into rg, for example: zcat app.log.gz | rg 'ERROR'.
Is the -P (PCRE2) flag always available in rg?
Only if ripgrep was compiled with PCRE2 support. Most distro packages include it; confirm with rg --pcre2-version. If the flag is absent, you'll get an error message saying PCRE2 is unavailable.
How do I search files ignored by .gitignore without rewriting my .gitignore?
Create a .ignore file in the project root (ripgrep and fd both read it). Entries in .ignore override .gitignore for search purposes without affecting git itself.

Related guides