TL;DR:

  • zoxide replaces cd with a frequency-weighted jump command that learns your most-used directories — z proj gets you to /home/user/work/projects/current-project after a few visits
  • fzf adds fuzzy-finding to your shell history, file paths, git branches, and anything else that produces a list — the Ctrl+R history search upgrade alone justifies installing it
  • bat, eza, and Starship are drop-in upgrades to cat, ls, and your prompt respectively, with syntax highlighting, git integration, and sensible defaults out of the box

There’s a specific kind of developer productivity tax that’s easy to miss: the accumulated friction of tools that work but could work better. You cd through five directories to get somewhere you’ve been a hundred times. You scroll through shell history squinting at timestamps. You cat a file and get an undifferentiated wall of text with no syntax highlighting.

None of these things are slow enough to notice in isolation. Together, across a day, they add up. These five tools address specific friction points and combine well — installing all five takes under ten minutes and the impact shows up the same day.

zoxide — smarter directory jumping

What it replaces: cd

The problem it solves: Navigating to deeply nested or frequently visited directories requires either remembering the full path, maintaining aliases, or using a file manager. None of these scale well as your directory structure grows.

How it works: zoxide tracks every directory you cd into, weighted by visit frequency and recency (a “frecency” score). Once you’ve visited /home/user/work/acme/backend/src a few times, z backend jumps directly there. If “backend” is ambiguous across multiple projects, z acme backend narrows it. zi opens an interactive picker for disambiguation.

Installation:

# Using Homebrew (macOS/Linux)
brew install zoxide

# Using cargo
cargo install zoxide --locked

# Add to ~/.zshrc or ~/.bashrc
eval "$(zoxide init zsh)"   # for zsh
eval "$(zoxide init bash)"  # for bash

Key config: Set _ZO_ECHO=1 if you want zoxide to print the directory it’s jumping to (useful while learning what it’s learned about your patterns). The database lives at ~/.local/share/zoxide/db.zo — it’s a SQLite file, so you can inspect or back it up.

The workflow change: After a week of use, z becomes faster than even tab-completing from the root. The 10 directories you actually visit daily take two to four characters each.

fzf — fuzzy finder for everything

What it replaces: Ctrl+R shell history, find, manual tab-completing through long lists

The problem it solves: Most shell interfaces for searching lists (history, files, processes, git branches) are either linear scrolling or require exact prefix matching. Neither handles large lists well.

How it works: fzf pipes any list into an interactive fuzzy-search interface. Type fragments and it filters in real time. The default key bindings replace shell history search (Ctrl+R), file path completion (Ctrl+T), and directory traversal (Alt+C).

Installation:

brew install fzf
# Then run the install script to set up key bindings and shell completion:
$(brew --prefix)/opt/fzf/install

Where fzf compounds: The real power is in combining it with other tools via pipes:

# Fuzzy-search git branches and check out the selected one
git branch | fzf | xargs git checkout

# Preview file contents while searching
fzf --preview 'bat --color=always {}'

# Kill a process by name without remembering its PID
ps aux | fzf | awk '{print $2}' | xargs kill

The --preview flag is worth spending time on. Pairing fzf with bat (below) for file previews turns file selection into a rapid browseable experience.

Shell history upgrade: After running the fzf install script, Ctrl+R opens fzf over your full history with fuzzy matching. You type fragments of a command — docker run redis, kubectl get pods, git rebase — and it finds the right entry immediately. This is the single change that most developers cite as the biggest immediate improvement.

bat — cat with syntax highlighting and git integration

What it replaces: cat, and to some extent less

The problem it solves: cat outputs raw text with no syntax highlighting, no line numbers, and no indication of which lines have changed since the last commit.

How it works: bat adds syntax highlighting for over 200 languages, line numbers, git diff indicators in the gutter, and automatic paging for files longer than the terminal window. It reads from file paths or stdin, so it drops in as a cat replacement in most contexts.

Installation:

brew install bat
# On Ubuntu/Debian: apt install bat (may install as batcat)

Useful configuration (~/.config/bat/config):

--theme="Dracula"
--style="numbers,changes,grid"
--pager="less -FR"

Aliasing: Most users alias cat to bat:

alias cat='bat'

Some prefer to keep cat for piping (bat adds colour codes that interfere with some pipes) and use a separate alias like b for interactive use. bat detects when it’s being piped and suppresses formatting automatically, so the alias is safe in most cases.

The fzf integration: fzf --preview 'bat --color=always --line-range :100 {}' gives you a live syntax-highlighted file preview as you fzf through a directory. This pair is worth setting up as a shell function.

eza — ls with modern defaults

What it replaces: ls

The problem it solves: ls has accumulated decades of flag complexity. Getting a useful view of a directory — file sizes in human-readable format, git status, sorted by modification time, with colours — requires a long flag string that most people put in an alias and forget the original meaning of.

How it works: eza is a modern ls rewrite (forked from the abandoned exa project) with sensible defaults, git integration, tree view, and active maintenance.

Installation:

brew install eza

Key flags:

eza --long --git --header        # long view with git status column and column headers
eza --tree --level 2             # tree view, two levels deep
eza --long --sort modified       # sort by modification time
eza -la                          # long view including hidden files (same -a as ls)

Standard aliases:

alias ls='eza'
alias ll='eza --long --git --header'
alias la='eza -la --git --header'
alias lt='eza --tree --level 2'

The git status column (--git) is the feature most developers find they use constantly — a one-glance view of which files in a directory are modified, untracked, or ignored.

Starship — a fast, informative prompt

What it replaces: Your existing shell prompt, any manual PS1 configuration

The problem it solves: A default bash or zsh prompt shows you a path and a cursor. A useful prompt shows you the current git branch, the status of the working tree, the active language version (Node.js, Python, Rust), whether you’re in a Nix shell or virtual environment, and the exit code of the last command — without being slow enough that you notice the rendering time.

How it works: Starship is a cross-shell prompt written in Rust. It renders fast, supports virtually every shell, and is configured via a single TOML file.

Installation:

brew install starship

# Add to end of ~/.zshrc
eval "$(starship init zsh)"

Minimal useful config (~/.config/starship.toml):

[git_branch]
symbol = " "

[git_status]
ahead = "⇡${count}"
behind = "⇣${count}"
modified = "!${count}"
untracked = "?${count}"

[nodejs]
symbol = " "

[python]
symbol = " "

Starship’s module documentation is comprehensive — it’s worth reading through to enable the modules relevant to your stack. If you work across multiple language environments, the language version display (showing Python 3.12.1 or Node 20.11.0 in the prompt) eliminates a common source of “why is this behaving differently” confusion.

Wiring them together

These tools multiply in value when combined:

# ~/.zshrc additions that connect the pieces

# zoxide replaces cd
eval "$(zoxide init zsh)"

# fzf key bindings
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh

# Preview files with bat when using fzf
export FZF_CTRL_T_OPTS="--preview 'bat --color=always --line-range :100 {}'"

# eza aliases
alias ls='eza'
alias ll='eza --long --git --header'
alias lt='eza --tree --level 2'

# bat as default pager for man pages
export MANPAGER="sh -c 'col -bx | bat -l man -p'"

# Starship prompt
eval "$(starship init zsh)"

Ten minutes of setup. Persistent value across every terminal session that follows.