Skip to main content

ripgrep — The Search Tool That Changed My WSL2 Workflow

Discover ripgrep, the blazingly fast grep alternative written in Rust. Installation on Ubuntu/WSL2, ZSH configuration, practical shell functions, and real-world use cases that will transform how you search code every day.

· 12 min read
View raw
Christophe
Markdown, WSL and Docker lover ~ Insatiable curious.
Claude Code
AI Assistant
Cet article existe aussi en français — lire la version française.

Changelog

Latest
The rc setup and the rgtodo/rgenv functions now come in a zsh and a bash version, in ~/.zsh/fns or ~/.bash/fns.
added a Docker-first demo to try before installing

ripgrep — The Search Tool That Changed My WSL2 Workflow

⚡
TL;DR

ripgrep (rg) is a line-oriented search tool written in Rust that dramatically outperforms traditional grep — especially on large codebases. It respects .gitignore by default, skips binary files, and handles Unicode natively. This article covers installation on Ubuntu/WSL2, a ~/.ripgreprc configuration file, ZSH aliases and functions for your ~/.zshrc and ~/.zsh/fns/ directory, and real-world use cases that will make you wonder how you ever lived without it.

Damn. I have typed that command so many times I could do it in my sleep: grep -rn --include="*.php" "getUser" . | grep -v vendor. It works — grep is a classic, it gets the job done. But over the years I have accumulated so many flags, so many aliases, so many muscle-memory workarounds for grep's quirks... At some point, you have to ask yourself: is there something better out there?

What does that command actually do?

grep -rn --include="*.php" "getUser" . breaks down as: -r searches recursively through all subdirectories, -n prints the line number of each match, --include="*.php" restricts the search to .php files only, "getUser" is the pattern to find, and . means start from the current directory. The trailing | grep -v vendor pipes the results through a second grep that filters OUT (-v) any line containing vendor — a common workaround to exclude the vendor/ dependencies folder that grep has no built-in way to skip.

There is. It is called ripgrep, and once you start using it, there is no going back.

It belongs to the same family of modern rewrites as eza (for ls) and delta (for git diff).

ripgrep vs grep — A Side-by-Side Look​

Let me show you why ripgrep clicks so immediately. Here are the same searches expressed in both tools:

With grep:

grep -rn --include="*.php" "getUser" . \
| grep -v vendor

With ripgrep:

rg "getUser" --type php

With grep:

grep -rn -i "TODO" . \
--include="*.py" \
--include="*.pyi" \
--exclude-dir=.venv \
--exclude-dir=dist

With ripgrep:

rg -i "TODO" --type py

With grep:

grep -rn "DB_PASSWORD" . \
--include="*.env" \
--include="*.yml" \
--include="*.yaml"

With ripgrep:

rg "DB_PASSWORD" -t yaml -t sh

For sure, grep's flags are powerful once you know them by heart. But ripgrep's syntax is just cleaner — and by default it already ignores vendor/, node_modules/, and everything in your .gitignore, without any extra flags.

What is ripgrep?​

ripgrep (rg on the command line) is a line-oriented search tool built in Rust by Andrew Gallant (BurntSushi). Speed is its headline feature — benchmarks consistently show it outperforming grep, ag (the Silver Searcher), and ack by a wide margin on large codebases — and hyperfine lets you check that claim on your own machine. But raw speed is only part of the story.

What makes ripgrep genuinely better for day-to-day use is its smart defaults:

  • It automatically respects .gitignore, .ignore, and .rgignore files — so vendor/, node_modules/, dist/, and .git/ are skipped without any --exclude-dir gymnastics.
  • It skips binary files by default.
  • It colors and groups output by filename out of the box.
  • It uses Unicode by default — no more garbled output on accented filenames or content.
  • It supports modern regex syntax, including PCRE2 with the -P flag.
  • It searches hidden files only when you explicitly ask for it (--hidden).

That last output behavior is underrated. Readable, grouped, colorized results — right away, no configuration required. So cool, no?

Seeing It in Action with Docker​

Before touching your own ~/.zshrc, try ripgrep in a throwaway container that already ships the ~/.ripgreprc config, the rgtodo/rgenv functions, and a small fake project built specifically to show off the smart defaults: a vendor/ folder that must stay invisible, TODO/FIXME comments, a leaking DB_PASSWORD, and a console.log(token) call worth flagging.

Why Docker first?

The Dockerfile below reproduces the exact .ripgreprc, .zsh/fns/ functions, and .zshrc section from this article, then builds the demo project. Nothing changes on your own machine.

Build and run it:

>user@machine: ~/ripgrep-demo

$ docker build -t ripgrep-demo . [+] Building 21.3s (10/10) FINISHED ✔ exporting to image

$ docker run --rm -it ripgrep-demo 🐳 root ~/demo #

Now play with the exact scenarios described above, live:

>user@machine: ~/yourproject

Notice vendor/legacy/User.php — which also defines getUser, on purpose — never shows up in any of these searches: the .gitignore and the --glob=!vendor/* config entry both exclude it, exactly as described above. Try rg --no-ignore "getUser" to see it reappear.

Installation on Ubuntu / WSL2​

ripgrep ships in Ubuntu's default package repositories, so getting it is a one-liner:

Prerequisite: ripgrep

>user@machine: ~/yourproject

Verify:

>user@machine: ~/yourproject

Want the latest version?

The Ubuntu repositories sometimes lag a few releases behind upstream ripgrep. For the most recent version, grab the latest .deb package directly from the GitHub releases page and install it with sudo dpkg -i ripgrep_*.deb. From version 14 onwards you also benefit from improved PCRE2 support and faster directory traversal.

Configuration — ~/.ripgreprc​

Like most Unix tools, ripgrep can be configured via a dedicated file. You point to it with the RIPGREP_CONFIG_PATH environment variable. Here an example of a ~/.ripgreprc file:

--colors=match:fg:red
--colors=match:style:bold
--colors=path:fg:blue
--follow
--glob=!.cache/*
--glob=!.git/*
--glob=!.output/*
--glob=!.yarn/*
--glob=!build/*
--glob=!dist/*
--glob=!node_modules/*
--glob=!vendor/*
--hidden
--max-filesize=10M
--smart-case

This is a configuration file for a search tool (likely ripgrep) that customizes how search results are displayed and which files to search. It sets color formatting for matches and file paths, includes hidden files in searches, excludes common build and dependency directories, follows symbolic links, and limits results to files under 10 megabytes.

Each line is a default flag applied to every rg invocation. A few highlights worth explaining:

  • --hidden makes ripgrep search inside hidden files and folders (anything prefixed with a dot). Combined with the --glob=!.git/* exclusion, this means you can search .env files, .zshrc, .gitignore, and similar without opening the entire .git/ internals.
  • --max-filesize=10M silently skips very large files (log dumps, generated assets) that would otherwise flood your results.
  • --smart-case is probably the most impactful setting: searches are case-insensitive by default, but the moment your pattern contains an uppercase letter ripgrep switches to exact case matching — exactly the behavior I want 95% of the time.

Shell Setup — ~/.zshrc or ~/.bashrc​

With the config file in place, I add the environment variable, the function loader, and a handful of type-specific aliases to my shell's rc file. The content is the same for zsh and bash, except for the loader loop:

export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"
# If you don't already have such loop...
# Load custom ZSH functions from ~/.zsh/fns/
for fn_file in ~/.zsh/fns/*.zsh; do
source "${fn_file}"
done
# Type-specific shortcuts
alias rgp='rg --type php'
alias rgj='rg --type js'
alias rgt='rg --type ts'
alias rgm='rg --type md'
alias rgy='rg --type yaml'
alias rgb='rg --type sh'
alias rgd='rg --glob "Dockerfile*" --glob "*.dockerfile"'

This script sets up a ripgrep configuration file path, loads custom ZSH functions from a directory, and creates convenient shortcut aliases for searching specific file types with ripgrep (a fast text search tool). It's basically building a personalized search environment with type-specific commands.

The for fn_file loop sources every function file in that directory automatically — so adding a new function is as simple as dropping a new file there, no need to touch your rc file again.

The type-specific aliases (rgp for PHP, rgt for TypeScript, rgm for Markdown, and so on) save keystrokes when you know exactly what kind of file you are looking in. I reach for rgp "getUser" or rgm "## Installation" dozens of times per day.

Shell Functions — ~/.zsh/fns/ or ~/.bash/fns/​

Now this is where it gets fun. Here are three functions I have installed in my ~/.zsh/fns/ directory, each solving a real problem.

rgf — Interactive Search with fzf​

I covered this combination in detail in a dedicated article: FZF + ripgrep: Interactive Code Search with Live Preview. The short version: pipe rg into fzf with a bat preview panel and you get an interactive fuzzy finder that searches file content, previews matches with syntax highlighting, and opens your editor at the exact matching line. If you have not read that article yet, it is worth the detour.

rgtodo — Surface All Technical Debt at Once​

Every codebase has them lurking in comments: TODO, FIXME, HACK, NOTE, XXX. This function brings them all to the surface:

Run rgtodo at the root of any project and you get a sorted, colorized list of every comment marker. Run rgtodo src/ to narrow it to a specific folder. Eye-opening on codebases that have been around for a while.

>user@machine: ~/yourproject

rgenv — Track Down Environment Variable Usage​

In Docker-heavy projects, environment variables end up scattered across .env files, compose.yml, PHP bootstraps, and Python configs. This function searches all of them in one shot:

rgenv DB_PASSWORD instantly shows every file that references that variable — whether it is defining it, consuming it, or passing it as a Docker secret. Invaluable when debugging configuration drift in complex multi-container setups.

>user@machine: ~/yourproject

Installing rgtodo and rgenv​

Both functions are plain shell code: the same files work in zsh and bash, only the folder differs. The loader loop is already in your rc file since the previous section.

1. Create the files in ~/.zsh/fns/:

📦 Project setup: ~/.zsh/fns
Help

Need help here? Click the "Help" button above for setup instructions.

2. Open a new terminal (or run exec zsh).

Check that the shell knows them: type rgtodo rgenv must answer … is a shell function twice.

Real-World Use Cases​

Let me walk through the searches I run most often in my day-to-day WSL2 workflow.

Finding a Function Definition Across a PHP Project​

rg "function getUser" --type php

Without ripgrep: add --include="*.php", --exclude-dir=vendor, -rn. With ripgrep: just type it. The vendor/ directory is already excluded because it is in .gitignore.

Searching All Docker Compose Files for an Image Reference​

rgy "image:"

That is our rgy alias (rg --type yaml). Useful when auditing a repository for outdated image references or finding all services that share a base image.

Context Lines — Understanding Code Around a Match​

The -C N flag shows N lines of context around each match — a feature I use constantly:

>user@machine: ~/yourproject

You can also use -A N (after) and -B N (before) separately, exactly like with grep.

A more common scenario: a teammate leaves console.log() calls in JavaScript before pushing to production. Without context, you only see the line itself — you know it exists, but not how serious it is. With -C 2:

>user@machine: ~/yourproject

Now you can tell the difference. The first match logs a JWT token in plain text — that is a security issue, not just noise. The second sits inside a for loop over a cart: one checkout call would flood the console with hundreds of lines. The context turns a list of matches into actionable information.

Counting Occurrences Per File​

rg -c "console.log" --type js

This outputs the match count per file — perfect for identifying which source files still have debug statements before a release.

Bulk Rename with rg + sed​

ripgrep is intentionally read-only (no -i in-place mode), but it pairs cleanly with sed for bulk refactoring:

>user@machine: ~/yourproject

rg -l lists only the filenames with matches. Piping to xargs sed -i then rewrites exactly those files — nothing more, nothing less. Much safer than a raw find | xargs sed that has no idea which files actually match.

Searching Multiple File Types at Once​

rg "API_KEY" -t php -t yaml -t sh

Multiple -t flags work together — ripgrep searches all matching types in a single pass, with unified colored output.

Limiting Search Depth​

rg "FROM" --glob "Dockerfile*" --max-depth 3

--max-depth is great when you want to stay close to the current directory without diving into every nested subdirectory.

Key Takeaways​

ripgrep quick reference

  • Install — sudo apt install ripgrep on Ubuntu/WSL2
  • Configure — create ~/.ripgreprc and point to it via RIPGREP_CONFIG_PATH in ~/.zshrc
  • Type flags — -t php instead of --include="*.php" — shorter and smarter
  • Smart case — --smart-case gives you case-insensitive searches that auto-switch to exact when you use uppercase
  • gitignore-aware — vendor/, node_modules/, .git/ are excluded automatically; no flags needed
  • Context lines — -C 3 shows 3 lines above and below each match
  • File list mode — rg -l returns filenames only; pipe to xargs for bulk operations
  • Interactive mode — rgf "pattern" combines ripgrep with fzf for live-preview search; full setup in FZF + ripgrep

Conclusion​

At the beginning of this article, I mentioned typing that grep -rn --include="*.php" ... | grep -v vendor command so many times it became muscle memory. Now, every time I open a terminal in WSL2, I reach for rg without even thinking. The vendor/ exclusion is automatic, the output is readable without any flags, and the search is genuinely faster on large codebases. Between the ~/.ripgreprc configuration, the type aliases in ~/.zshrc, functions like rgtodo and rgenv in ~/.zsh/fns/, and the rgf interactive search covered in a dedicated article, ripgrep has stopped being a tool I consciously choose and started being the invisible backbone of how I navigate code every day. If you are still on bare grep in your WSL2 environment, give yourself twenty minutes with ripgrep — that is all it takes.


🦋 Join the conversation on BlueskyRequires a free Bluesky account to like, repost or comment. This block talks to Bluesky's public API directly from your browser (no cookies, no sign-in) — that means your IP address reaches Bluesky's servers just by this section loading.

Loading comments…

Was this article helpful?
Did you try the steps in this article?