You have already typed the command you are about to type. Possibly last Tuesday, at three in the morning, with the exact flags that worked. Shell history is the difference between finding it in two seconds and reconstructing it from memory — but only if you know how to search it and have configured it so it is still there.
Everything below is bash unless stated otherwise. The zsh and fish differences are at the end.
Ctrl+R: the one to learn first
Press Ctrl+R and start typing any fragment of a command you ran before. The most recent match appears. What happens next is what most people never learn:
| Key | Effect |
|---|---|
Ctrl+R again | Step back to the next older match |
Enter | Run it immediately |
→ or Ctrl+E | Accept it into the prompt so you can edit before running |
Ctrl+G | Cancel and restore what you were typing |
Ctrl+C | Cancel and clear the line |
The arrow-key exit is the important one. Recalling a long rsync or ssh command and changing one hostname before running it is the whole point; pressing Enter by reflex is how you rsync to the wrong server.
Ctrl+S is meant to search forwards, and on most terminals it appears to freeze the session instead. That is legacy flow control — Ctrl+Q unfreezes it. To reclaim the key, put stty -ixon in your ~/.bashrc.
History expansion
The ! forms are shortcuts the shell expands before running the line. Three of them repay learning immediately; the rest are worth knowing exist.
| Form | Means | Typical use |
|---|---|---|
!! | The whole previous command | sudo !! after permission denied |
!$ | Last argument of the previous command | mkdir /srv/app then cd !$ |
!* | All arguments of the previous command | ls a.txt b.txt then rm !* |
!ssh | Most recent command starting with ssh | Re-running a connection |
!?prod? | Most recent command containing prod | When you forget how it started |
!123 | Command number 123 from history | Precise recall |
!-2 | Two commands back | Alternating between two commands |
^old^new | Rerun the last command with one substitution | Fixing a typo |
Append :p to print instead of run. !rm:p shows you which rm it matched and puts it in your history without executing it. Getting into that habit before !rm, !dd or !?prod? costs one keystroke and has saved a lot of people a lot of restores.
Expansion happens inside double quotes too, which is why echo "Hello!" sometimes produces an error about an event not found. Single quotes are safe. If the whole feature annoys you, set +H turns it off.
The history command itself
history # everything, numbered
history 20 # the last twenty
history | grep rsync # find that command
history -d 512 # delete entry 512 (the password you just typed)
history -d -5--1 # delete the last five entries
history -c # clear the in-memory list
history -a # append this session's new lines to the file now
history -r # read the file into this sessionhistory -d removes the line from memory. It is written to the file at the same points as everything else, so if you need it gone permanently, run history -d N && history -w.
Why your history keeps disappearing
By default bash keeps history in memory and writes it to ~/.bash_history only when the shell exits cleanly — and it overwrites the file rather than appending. So with three terminals open, the last one you close wins and the other two sessions are gone. A terminal killed by closing the window, a dropped SSH connection, or a reboot writes nothing at all.
Two settings fix it: histappend so sessions add rather than replace, and a history -a on every prompt so lines are written as you go instead of at exit.
Configuration worth having
# ~/.bashrc
HISTSIZE=100000 # lines kept in memory
HISTFILESIZE=200000 # lines kept in the file
HISTCONTROL=ignoreboth # ignorespace + ignoredups
HISTIGNORE="ls:ll:cd:pwd:exit:clear:history"
HISTTIMEFORMAT="%F %T " # timestamps in history output
shopt -s histappend # append, do not overwrite
shopt -s cmdhist # keep multi-line commands as one entry
PROMPT_COMMAND="history -a; ${PROMPT_COMMAND:-}"A note on each of the less obvious ones:
HISTSIZElarge — the default of 500 or 1000 is why history feels useless. Storage is not the constraint; a hundred thousand lines is a few megabytes.ignoreboth— drops consecutive duplicates, and drops any command you typed with a leading space. That leading space is the deliberate way to keep something out of history.HISTIGNORE— keeps the noise out so searching finds real commands. Do not get carried away: excluding too much makes history an unreliable record of what you did.HISTTIMEFORMAT— turns history into a timeline. Genuinely useful when reconstructing what you changed on a server and when.PROMPT_COMMAND— the${PROMPT_COMMAND:-}part preserves anything your distribution or prompt theme already set. Overwriting it blindly breaks titles and prompts.
People often add history -c; history -r to PROMPT_COMMAND to share history live across terminals. It works, but every prompt then reloads the entire file, and your up-arrow becomes other terminals’ commands rather than your own. Appending only is the calmer choice.
Secrets in history
~/.bash_history is a plain text file in your home directory, and it is where API tokens and database passwords go to live forever. Prefix anything sensitive with a space (with ignorespace set), or better, do not put secrets on a command line at all — while a command runs, its full arguments are visible in ps to every user on the machine. Read the value from a file or an environment variable instead.
If one gets in there anyway: history -d the entry, history -w, then rotate the credential. Assume it leaked.
Editing the line you recalled
History is only half of it. Readline — the library bash uses for the prompt — gives you these everywhere, including in python, psql and most other interactive tools.
| Key | Does |
|---|---|
Ctrl+A / Ctrl+E | Start / end of line |
Alt+B / Alt+F | Back / forward one word |
Ctrl+W | Delete the word before the cursor |
Ctrl+U / Ctrl+K | Delete to start / end of line |
Ctrl+Y | Paste back what you just deleted |
Alt+. | Insert the last argument of the previous command — press again for the one before that |
Ctrl+_ | Undo |
Ctrl+X Ctrl+E | Open the current line in $EDITOR; saving runs it |
Alt+. is the one that changes how you work. Ctrl+X Ctrl+E is the one for the moment a one-liner has grown into something that needs a real editor — see vim and nano. If you prefer modal editing, set -o vi switches readline to vi keybindings; put it in ~/.inputrc as set editing-mode vi to get it in every readline program.
One more ~/.inputrc addition worth the trouble — type a few characters and let the arrow keys search only matching commands:
# ~/.inputrc
"\e[A": history-search-backward
"\e[B": history-search-forwardzsh and fish
zsh uses different variable names and needs its history file set explicitly — several distributions ship a zsh with no persistent history at all until you do:
# ~/.zshrc
HISTFILE=~/.zsh_history
HISTSIZE=100000
SAVEHIST=100000
setopt INC_APPEND_HISTORY HIST_IGNORE_SPACE HIST_IGNORE_ALL_DUPS
setopt EXTENDED_HISTORY # record timestamps
# setopt SHARE_HISTORY # live sharing between sessions, if you want itfish needs none of this. History is written immediately, shared between sessions, and the up arrow already searches on what you have typed. Alt+. and most readline keys work; the configuration section above simply does not apply. There is more on the differences in What a Shell Actually Is.
When Ctrl+R is not enough
Once history holds a hundred thousand lines, stepping backwards one match at a time stops scaling. fzf replaces Ctrl+R with a fuzzy, scrollable list of every match at once, which is the single biggest upgrade available here and takes one line to install. If you want history synced across machines and stored in a database with proper search, atuin goes further — at the cost of a daemon and a syncing service in your shell startup.
Related
- What a Shell Actually Is — which startup file runs when, and why your settings sometimes do not load
- Linux Command Line Basics — if the prompt is still new
- fzf — fuzzy history search and much else
- grep — for searching the history file directly
