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.
# Old way — grep recursively for a function name, excluding junk dirsgrep -r "handle_connection" . --include="*.go" --color=auto
# New wayrg "handle_connection" --type goThe --type flag is the killer feature. rg --type-list shows you 50+ predefined file types. No more --include="*.go" --include="*.mod" incantations.
# Find TODOs in your entire project, excluding vendorrg "TODO|FIXME|HACK" --type py
# Case-insensitive search with context linesrg -i "database_url" -A 2 -B 2
# Count matches per filerg "import" --count --type js
# Search only in specific dirsrg "error" ./src ./cmdThe 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.
# Find all Python files in current dir, excluding .git and venvfind . -name "*.py" -not -path "*/.git/*" -not -path "*/venv/*"
# Same thing with fdfd -e pyfd 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).
# Find files modified in the last 24 hoursfd --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 filesfd -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.
# cat output, no contextcat src/main.rs
# bat output: syntax highlighted, line numbers, git change markers in gutterbat src/main.rs
# Paged view of a big filebat --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 pipingbat -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
# Search for pattern, preview matching files with syntax highlightingrg "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.
# Classic ls -la vs ezals -laeza -la --icons --git
# Tree view (replaces the tree command)eza --tree --level=2
# Sort by modified time, newest firsteza -la --sort=modified
# Show only directorieseza -lD
# Long format with git status columneza -la --gitThe 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.
# 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.goWhen 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.
# Basic usage: pipe a list in, interactively filter itls | fzf
# Open the selected file in vimvim $(fzf)
# Search command history interactively (replaces Ctrl+R)# Put this in your shell config — it overrides the default history searchThe 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:
# fzf + ripgrep: interactively grep your codebaserg --line-number "" | fzf --delimiter=: --preview 'bat --color=always {1} --highlight-line {2}'
# fzf + git: checkout a branch interactivelygit branch | fzf | xargs git checkout
# fzf + git log: browse commits, preview diffgit log --oneline | fzf --preview 'git show --color=always {1}'
# fzf + ps: kill a process interactivelyps aux | fzf | awk '{print $2}' | xargs kill
# fzf + fd: open any file in your projectfd -t f | fzf | xargs $EDITORThe 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.
# After visiting ~/projects/infra/docker/staging several timesz staging # jumps therez docker st # multi-token fuzzy matchdust — du that produces a readable tree instead of a wall of numbers. dust ~/Downloads immediately shows you what’s eating your disk.
sd — sed 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:
- No universal availability. None of these are installed by default anywhere. Every machine, container, and VM needs a fresh install. For anything distributed — scripts, Dockerfiles, remote server tooling — you’re either shipping binaries or depending on package managers.
- Version fragmentation.
exagoing dormant and spawningezais a preview of what happens when Rust hobby projects lose maintainers. - The performance arguments don’t always matter. If your grep takes 200ms instead of 800ms, but you’re running it interactively once a day, you have not improved your life.
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:
# 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 openalias fopen='fd -t f | fzf | xargs $EDITOR'
# Interactive branch checkoutalias 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:
# Debian/Ubuntusudo 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 Ubuntusudo apt install -y gpgsudo mkdir -p /etc/apt/keyringswget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc \ | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpgecho "deb [arch=amd64 signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" \ | sudo tee /etc/apt/sources.list.d/gierens.listsudo apt update && sudo apt install -y eza# Arch / Manjaro / CachyOSsudo pacman -S ripgrep fd bat fzf eza zoxide# macOS (Homebrew)brew install ripgrep fd bat fzf eza zoxide$(brew --prefix)/opt/fzf/install # installs shell key bindings# 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 zoxideAfter 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.