Every developer has done it. You ran a complex command ten minutes ago -- maybe a multi-flag curl, a gnarly find pipeline, or an awk one-liner that took three tries to get right. Now you need it again. You press the up arrow. And you press it again. And again. Forty commands later, you give up and retype the whole thing from scratch.

Your shell history is one of the most powerful productivity tools you already have installed. Most developers barely scratch the surface with Ctrl+R. This post goes deeper -- into history expansion, fuzzy search with fzf, per-directory history, and the configuration tweaks that make your history actually useful.

If you haven't already, check out our first post on 10 Terminal Shortcuts That Will Save You Hours Every Week for the foundational shortcuts that pair well with everything here.

History Expansion: Your Shell's Built-In Recall System

Before you install anything, your shell already has a rich history expansion system. Most developers know !! (repeat last command), but that's just the beginning.

The Essentials

# Repeat the entire last command
sudo !!
# Expands to: sudo <whatever you just ran>

# Grab the last argument of the previous command
mkdir -p src/components/analytics/dashboard
cd !$
# Expands to: cd src/components/analytics/dashboard

# Grab the first argument of the previous command
ls -la /var/log/nginx/access.log
cat !^
# Expands to: cat /var/log/nginx/access.log

Going Further

The real power is in the less-known expansions:

# Run the command from 3 commands ago
!-3

# Run the most recent command starting with "docker"
!docker

# Run the most recent command containing "deploy"
!?deploy?

# Grab ALL arguments from the last command
echo one two three
printf "%s\n" !*
# Expands to: printf "%s\n" one two three

# Grab a specific argument by position (0-indexed)
tar -czf backup.tar.gz /home/user/documents
echo !:2
# Expands to: echo backup.tar.gz

Safe Mode: Verify Before Executing

History expansion can be dangerous if you're not sure what it will expand to. Add :p to print the expansion without running it:

# Preview what !docker would run
!docker:p
# Prints: docker compose up -d --build
# Now press up arrow to edit or Enter to run

This is critical for production environments. Never run !rm blind on a server. Always !rm:p first.

Fuzzy History Search with fzf

Ctrl+R is fine for commands you remember part of. But when you're searching through thousands of history entries, you need something smarter. fzf transforms your shell history into a fuzzy-searchable, interactive list.

Installation

# macOS
brew install fzf
$(brew --prefix)/opt/fzf/install

# Ubuntu/Debian
sudo apt install fzf

# From source (any platform)
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install

Once installed, Ctrl+R gets replaced with fzf's interactive fuzzy finder. The difference is night and day:

Advanced fzf History Usage

# Set fzf defaults for history search in your .zshrc or .bashrc
export FZF_CTRL_R_OPTS="
  --preview 'echo {}'
  --preview-window up:3:hidden:wrap
  --bind 'ctrl-/:toggle-preview'
  --bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'
  --header 'Press CTRL-Y to copy to clipboard, CTRL-/ to toggle preview'
"

This gives you a preview pane, clipboard copy support, and a help header -- all inside your history search.

Combining fzf with History Commands

# Search history and immediately edit the selected command before running
fzf_history_edit() {
  local selected
  selected=$(fc -rl 1 | fzf --no-sort --tac | sed 's/^ *[0-9]* *//')
  if [[ -n "$selected" ]]; then
    print -z "$selected"
  fi
}
alias hh='fzf_history_edit'

Now hh opens your full history in fzf, and the selected command is placed on your command line ready to edit before execution.

hstr: A Dedicated History Manager

If you want a focused history tool without fzf's broader scope, hstr is purpose-built for shell history management. It offers three search modes (exact, regex, fuzzy), bookmarking, and the ability to delete specific entries.

# macOS
brew install hstr

# Ubuntu/Debian
sudo apt install hstr

# Configure it to replace Ctrl+R
hstr --show-configuration >> ~/.bashrc
source ~/.bashrc

hstr is particularly good at one thing fzf doesn't do out of the box: letting you delete embarrassing or dangerous commands from your history. Select an entry and press Del to remove it permanently.


What if you didn't have to search at all? RewriteCmd takes a different approach: instead of digging through history for a command you ran before, you describe what you want in plain English and get the exact command back. No memorization. No searching. Just results.

curl -fsSL https://rewritecmd.com/install | sh

Per-Directory History

Here's a scenario: you work on five different projects. Each project has its own set of commands -- different Docker setups, different test runners, different deploy scripts. But your shell history is one global list. When you're in your Python project and press Ctrl+R, you're wading through Node.js commands from two hours ago.

Per-directory history solves this. Your shell records separate history for each directory you work in.

Zsh: zsh-per-directory-history

# Install via your Zsh plugin manager (e.g., zinit)
zinit light jimhester/per-directory-history

# Or clone manually
git clone https://github.com/jimhester/per-directory-history.git \
  ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/per-directory-history

Once enabled, Ctrl+R searches your current directory's history by default. Press Ctrl+G to toggle between per-directory and global history. This means when you're in ~/projects/api-server, your history search only shows commands you've run in that directory.

Bash: Manual Approach

Bash doesn't have a plugin ecosystem like Zsh, but you can wire up per-directory history with a function:

# Add to ~/.bashrc
__per_dir_history() {
  local dir_hash
  dir_hash=$(echo -n "$PWD" | md5sum | cut -d' ' -f1)
  export HISTFILE="$HOME/.bash_history_dirs/$dir_hash"
  mkdir -p "$HOME/.bash_history_dirs"
  history -r
}

PROMPT_COMMAND="__per_dir_history;$PROMPT_COMMAND"

This switches your HISTFILE every time you cd, giving each directory its own history file.

Tuning Your History Configuration

The default history settings on most systems are far too conservative. Here's what to change and why.

Bash Configuration

# Add to ~/.bashrc

# Store 100,000 commands in memory and on disk
HISTSIZE=100000
HISTFILESIZE=200000

# Ignore duplicate commands and commands starting with a space
HISTCONTROL=ignoreboth

# Append to history file instead of overwriting (critical for multiple terminals)
shopt -s histappend

# Save multi-line commands as a single entry
shopt -s cmdhist

# Record timestamps for each command
HISTTIMEFORMAT="%F %T  "

# Ignore trivial commands
HISTIGNORE="ls:cd:pwd:exit:clear:history"

# Write history after every command (not just on shell exit)
PROMPT_COMMAND="history -a; $PROMPT_COMMAND"

Zsh Configuration

# Add to ~/.zshrc

HISTSIZE=100000
SAVEHIST=200000
HISTFILE=~/.zsh_history

# Share history across all terminal sessions
setopt SHARE_HISTORY

# Don't store duplicate commands
setopt HIST_IGNORE_ALL_DUPS

# Remove older duplicate when a new duplicate is added
setopt HIST_IGNORE_DUPS

# Don't record commands starting with a space
setopt HIST_IGNORE_SPACE

# Write history incrementally (not just on exit)
setopt INC_APPEND_HISTORY

# Record timestamps
setopt EXTENDED_HISTORY

# Remove extra blanks from commands
setopt HIST_REDUCE_BLANKS

The single most impactful change: increase HISTSIZE to 100,000+ and enable histappend (Bash) or INC_APPEND_HISTORY (Zsh). Without these, you lose history every time you close a terminal, and you cap out at 500-2,000 entries by default. That's maybe two days of work for an active developer.

History Tools Compared

Feature Ctrl+R (built-in) fzf hstr RewriteCmd
Fuzzy matching No (exact substring) Yes Yes N/A (natural language)
Visual interface No (inline) Yes (full-screen) Yes (full-screen) Yes (inline)
Delete entries No No Yes N/A
Bookmark commands No No Yes N/A
Per-directory history No No No N/A
Works without history No No No Yes
Handles commands you've never run No No No Yes
Install required No Yes Yes Yes
Learning curve Low Low Low None

The key differentiator in the last two rows: every history tool assumes you've run the command before. When you need a command you've never typed -- or one you ran six months ago on a different machine -- history search hits a wall. That's where a tool like RewriteCmd fills the gap: describe the task, get the command.

Putting It All Together

Here's the setup I'd recommend for most developers:

  1. Tune your history config (5 minutes, one-time) -- increase sizes, enable append mode, add timestamps
  2. Install fzf (2 minutes) -- replaces Ctrl+R with something dramatically better
  3. Learn five history expansions -- !!, !$, !string, !:2, and !cmd:p
  4. Add per-directory history if you work across multiple projects
  5. Install RewriteCmd for the commands that aren't in your history

Each layer handles a different scenario. History expansion is instant for patterns you use constantly. fzf handles the "I ran this recently but don't remember the exact syntax" case. Per-directory history keeps context clean. And RewriteCmd handles everything else -- the command you need but have never typed before.

# Get RewriteCmd
curl -fsSL https://rewritecmd.com/install | sh

What's your history setup? Are you still on vanilla Ctrl+R, or have you customized your workflow? Share your .bashrc / .zshrc history config -- we're always looking for tricks we haven't seen.