Skip to content
Go back

The Modern Unix Toolkit: fzf, ripgrep, fd, bat, eza

By SumGuy 11 min read
The Modern Unix Toolkit: fzf, ripgrep, fd, bat, eza

Your Shell From 1974 Called. It Wants Its Tools Back.

Here’s the honest truth: grep, find, ls, and cat work. They always work. They’re on every machine you’ll ever SSH into, they’re in every tutorial ever written, and they’ll outlive us all. Nobody is taking them away.

But “works” and “pleasant to use” are not the same thing. find . -name "*.py" -not -path "*/node_modules/*" -not -path "*/.git/*" is technically correct and also a small crime against ergonomics. grep -r "someFunction" . --include="*.ts" --color=auto gets the job done while making you feel like you’re submitting a tax form.

The Rust generation of CLI tools did something simple: they kept the core job, threw out 50 years of accumulated interface debt, and added the things you actually want — color by default, sane defaults, fast-as-hell performance, and output you can read without squinting.

This is a tour of the ones worth keeping permanently. Not hype. Actual comparisons.


ripgrep: grep That Respects Your Time

rg is what grep -r should have been. It’s recursive by default, respects .gitignore automatically, skips binary files without being asked, and is faster than grep by a margin that’s embarrassing.

Terminal window
# Old way — grep recursively for a function name, excluding junk dirs
grep -r "handle_connection" . --include="*.go" --color=auto
# New way
rg "handle_connection" --type go

The --type flag is the killer feature. rg --type-list shows you 50+ predefined file types. No more --include="*.go" --include="*.mod" incantations.

Terminal window
# Find TODOs in your entire project, excluding vendor
rg "TODO|FIXME|HACK" --type py
# Case-insensitive search with context lines
rg -i "database_url" -A 2 -B 2
# Count matches per file
rg "import" --count --type js
# Search only in specific dirs
rg "error" ./src ./cmd

The real win: rg is roughly 3–5x faster than grep -r on large repos because it uses SIMD, parallelizes across CPU cores, and skips .git/ and node_modules/ without you asking. On a monorepo with 200k files, that’s the difference between “instant” and “time to check Twitter.”

When to keep grep: You’re on a server where you can’t install anything. Remote systems, containers without package managers, friend’s machine. grep is universal. rg is not.


fd: find That Doesn’t Make You Read the Manual Every Time

find is a POSIX utility that predates most of the people reading this. It works. It also has an argument ordering that feels like it was designed to punish you.

Terminal window
# Find all Python files in current dir, excluding .git and venv
find . -name "*.py" -not -path "*/.git/*" -not -path "*/venv/*"
# Same thing with fd
fd -e py

fd is recursive by default, ignores hidden dirs and .gitignore entries by default, and uses regex patterns by default (with literal -g glob mode if you want it).

Terminal window
# Find files modified in the last 24 hours
fd --changed-within 1d
# Find directories named "cache"
fd -t d cache
# Execute a command on results (like find -exec, but readable)
fd -e log -x rm {}
# Case-insensitive search (automatic if pattern is all lowercase)
fd dockerfile
# Include hidden files
fd -H ".env"

The hidden file behavior is worth calling out: fd by default hides dotfiles and .gitignore-listed paths, just like rg. You get clean results without explicitly excluding node_modules and .git every single time. You opt IN to seeing hidden files with -H, rather than opting out of junk.

When to keep find: Scripts that run on arbitrary systems. Anything in a cron job, Docker image, or CI pipeline where you control nothing about the environment. find is POSIX. fd is not installed by default anywhere.


bat: cat With a Brain

bat is cat with syntax highlighting, line numbers, git diff integration, and pager support. That’s the whole pitch.

Terminal window
# cat output, no context
cat src/main.rs
# bat output: syntax highlighted, line numbers, git change markers in gutter
bat src/main.rs
# Paged view of a big file
bat --paging always huge_log.txt
# Show non-printable characters (like cat -A)
bat -A config_with_weird_whitespace.yaml
# Plain output (no decorations) — useful for piping
bat -p src/main.rs | grep "fn "

The git integration is genuinely useful: if a file is in a git repo, bat shows a +, ~, or - in the gutter for added, modified, or deleted lines versus HEAD. You get a lightweight git diff view just from opening a file.

The killer combo: bat + ripgrep

Terminal window
# Search for pattern, preview matching files with syntax highlighting
rg "panic!" --files-with-matches | xargs bat
# Or with fzf in the middle (more on this below)
rg "TODO" -l | fzf --preview 'bat --color=always {}'

When to keep cat: Piping binary data. Concatenating files together. Any context where you’re just moving bytes around and don’t need the decoration. bat adds overhead and its plain mode (-p) is basically cat anyway.


eza: ls That Shows You What You Actually Want

eza is the maintained fork of exa (which went dormant). It’s ls with sensible defaults: color by default, file type icons (with a Nerd Font), git status in the listing, human-readable sizes without -h, and a tree view that doesn’t require a separate tree install.

Terminal window
# Classic ls -la vs eza
ls -la
eza -la --icons --git
# Tree view (replaces the tree command)
eza --tree --level=2
# Sort by modified time, newest first
eza -la --sort=modified
# Show only directories
eza -lD
# Long format with git status column
eza -la --git

The git status column is the one I actually use daily. Every file gets a two-character git status in the listing — staged, unstaged, ignored. No more git status to check if you forgot to stage something before a commit.

Terminal window
# What the git column looks like
# N = new, M = modified, D = deleted, I = ignored
# drwxr-xr-x - user 2026-05-07 14:22 NM src/
# .rw-r--r-- 2.3k user 2026-05-07 14:20 M main.go

When to keep ls: Same answer as the others. Remote servers, scripts, anything POSIX-portable. Also: if you’ve been using ls for 20 years and muscle memory is load-bearing, ls is fine. Nobody is making you switch.


fzf: The Tool That Changes How You Use Your Shell

Everything above is “better version of X.” fzf is different. It’s a fuzzy finder that you pipe things into, and it transforms how you interact with your shell history, files, git branches, processes, and basically anything that produces a list.

Terminal window
# Basic usage: pipe a list in, interactively filter it
ls | fzf
# Open the selected file in vim
vim $(fzf)
# Search command history interactively (replaces Ctrl+R)
# Put this in your shell config — it overrides the default history search

The history search alone is worth installing fzf. The default shell Ctrl+R does substring search on your history, one result at a time. fzf’s Ctrl+R gives you fuzzy search across your entire history with a live-updating preview. If you type docker run priv, you’ll find docker run --privileged -it ... from three months ago.

The killer combos:

Terminal window
# fzf + ripgrep: interactively grep your codebase
rg --line-number "" | fzf --delimiter=: --preview 'bat --color=always {1} --highlight-line {2}'
# fzf + git: checkout a branch interactively
git branch | fzf | xargs git checkout
# fzf + git log: browse commits, preview diff
git log --oneline | fzf --preview 'git show --color=always {1}'
# fzf + ps: kill a process interactively
ps aux | fzf | awk '{print $2}' | xargs kill
# fzf + fd: open any file in your project
fd -t f | fzf | xargs $EDITOR

The preview window is what separates fzf from “just another filter.” You can pass --preview 'bat --color=always {}' to get a live syntax-highlighted preview of whatever file is selected as you move through the list. It sounds like a parlor trick until you’re navigating a 300-file project by fuzzy name with live preview, and then it’s your new baseline.


The Supporting Cast (Worth Knowing)

zoxide — smarter cd. Learns which directories you visit and lets you jump with z docker instead of cd ~/projects/infra/docker/staging. It’s like autojump but written in Rust and faster.

Terminal window
# After visiting ~/projects/infra/docker/staging several times
z staging # jumps there
z docker st # multi-token fuzzy match

dustdu that produces a readable tree instead of a wall of numbers. dust ~/Downloads immediately shows you what’s eating your disk.

sdsed with a sane syntax. sd 'foo' 'bar' file.txt instead of sed -i 's/foo/bar/g' file.txt. The difference feels small until you’re doing complex regex replacements and you remember you don’t have to escape every slash.

hyperfine — benchmarking for shell commands. hyperfine 'grep -r foo .' 'rg foo' runs both N times, warms up the filesystem cache, and gives you proper statistical output. Useful when you’re arguing about whether something is actually faster.


The “Rust Everywhere Is a Cult” Critique (Fairly Stated)

This is a reasonable thing to say. The Rust CLI tool space has some real problems:

The reasonable position is: use these tools on machines you control daily (your laptop, your primary dev VM). Don’t let them creep into shared scripts and infrastructure tooling where portability matters. Keep grep, find, awk, and sed fluent — they’re what you’ll have when nothing else is available.


Sane Shell Aliases

Put these in your ~/.zshrc or ~/.bashrc:

~/.zshrc
# Drop-in replacements (safe to alias — same basic usage)
alias ls='eza --icons'
alias ll='eza -la --icons --git'
alias lt='eza --tree --level=2 --icons'
alias cat='bat --paging=never'
alias grep='rg'
alias find='fd'
# fzf history search (overrides Ctrl+R with fuzzy version)
# Install fzf then source its shell integration
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
# zoxide (replaces cd after init)
eval "$(zoxide init zsh)"
# Quick project file open
alias fopen='fd -t f | fzf | xargs $EDITOR'
# Interactive branch checkout
alias gcob='git branch | fzf | xargs git checkout'

One honest warning about aliasing cat to bat: some scripts pipe through cat expecting raw output. bat -p (plain mode) is cleaner for piping. If you alias cat='bat --paging=never -p' you get highlighting in interactive use but safe piping behavior — but you also lose the git gutter. Pick your trade-off.


The Minimal Install Script (Fresh Machine Starting Point)

You’re on a new box. Here’s the fastest path to all of these:

Terminal window
# Debian/Ubuntu
sudo apt install -y ripgrep fd-find bat fzf zoxide
# Note: on Ubuntu, fd is installed as fdfind (name conflict with another pkg)
# Add alias: alias fd='fdfind'
# eza needs a PPA or manual install on Ubuntu
sudo apt install -y gpg
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc \
| sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" \
| sudo tee /etc/apt/sources.list.d/gierens.list
sudo apt update && sudo apt install -y eza
Terminal window
# Arch / Manjaro / CachyOS
sudo pacman -S ripgrep fd bat fzf eza zoxide
Terminal window
# macOS (Homebrew)
brew install ripgrep fd bat fzf eza zoxide
$(brew --prefix)/opt/fzf/install # installs shell key bindings
Terminal window
# Universal: Cargo (if you have Rust installed)
cargo install ripgrep fd-find bat eza zoxide
# fzf: use package manager or GitHub releases — cargo build is slow for fzf
# Or use cargo-binstall for prebuilt binaries (much faster)
cargo binstall ripgrep fd-find bat eza zoxide

After install, add the aliases block above to your shell config, run source ~/.zshrc, and you’re done.


The Actual Decision

Install all of these on your daily driver. Learn rg and fzf first — they have the highest surface area improvement per minute of learning. bat and eza are drag-and-drop replacements that require zero new knowledge. fd will click once you stop expecting find syntax.

Keep the originals fluent. When you SSH into a server at 2 AM because something’s on fire, you will not have rg installed, and you will not have time to install it. grep -r and find . -name will still save you.

The tools coexist fine. That’s the actual cult-free take: use better tools where you can, know the originals when you must.

Your terminal deserves a small upgrade.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
tmux vs Zellij vs Screen: Pick Your Multiplexer
Next Post
Frigate + Coral TPU: AI Cameras Without the Subscription

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts