linux · difficulty ◆
history — your own command logbook
That perfect 12-flag command you ran last week is one Ctrl-R away — you just never learned to ask.
Ctrl-R won't just find the command — it finds the one you forgot you even typed.
AI-generated draft · reviewed and editedThis text was generated with AI as a first draft, then reviewed and edited by the author, who holds editorial responsibility for it.$ historyIt's more than a diary
Every interactive bash session appends each line you type to an in-memory list, and on exit writes it to ~/.bash_history. The `history` command prints that list, numbered; `!42` re-runs entry 42; `!grep` re-runs the last command starting with grep. The real unlock is that this isn't a read-only archive — HISTFILE, HISTSIZE, and HISTCONTROL shape what gets remembered and what's silently skipped.
Search is the superpower
The single most useful terminal habit is Ctrl-R: press it, type two or three characters, and bash walks you back through every matching command you've ever run. Tap Ctrl-R again to go further back, Enter to run. Combined with `!$` (last argument of the previous command) and `!!` (the whole previous command), you can replay a long pipeline without touching the keyboard twice.
One history per shell — and how to merge or scrub them
Each shell keeps its own in-memory list and writes it out only when something tells it to, which is why two open terminals can step on each other. The behaviour is the `histappend` shell option: set it and bash appends this session's list to $HISTFILE when the shell exits; leave it unset and bash rewrites the file from the list instead, so the other window's commands disappear. Ubuntu's default ~/.bashrc sets it, two lines below HISTCONTROL. Three builtins move the lines by hand: `history -a` appends this session's new lines to the file immediately (verified here — the file had them while the shell was still running), `history -n` pulls in whatever other shells have appended since the last check, and `history -w` writes the current list over the file. The idiom that ties them together is `PROMPT_COMMAND='history -a'` in ~/.bashrc: PROMPT_COMMAND runs before every prompt, so each command is on disk before you type the next one and the other terminal can pick it up with `history -n`. For the opposite problem — a history that holds too much — HISTIGNORE takes colon-separated globs matched against the whole line (`HISTIGNORE='ls:cd:history*'`), HISTCONTROL=erasedups removes any earlier copy of a command before saving the new one, and HISTTIMEFORMAT stamps every entry with the time it ran.
Example
$ $ history | tail -6 123 cd /var/log
124 du -sh *
125 find /home -name '*.conf' -size +1M
126 journalctl -u nginx --since today --no-pager
127 sudo apt upgrade
128 history | tail -6The numbered list. The numbers are the keys: `!125` re-runs that exact find one-liner.
$ $ !126journalctl -u nginx --since today --no-pager
Sep 20 09:00:02 srv nginx[1282]: 10.0.0.7 - - [20/Sep/2026:09:00:02] "GET /health HTTP/1.1" 200 18!<n> re-executes command n. Bash echoes the expansion first, then runs it — no ambiguity about what you're firing.
$ $ tar czf backup-2026.tar.gz src/
$ du -sh !$du -sh backup-2026.tar.gz
1.2M backup-2026.tar.gz!$ expands to the last word of the previous command — here the archive filename. `!!` expands to the entire previous line; `sudo !!` reruns it as root.
$ $ fc -l 20 2520 ls -la
21 cd projects
22 git status
23 git log --oneline -5
24 cat README.md
25 npm testfc lists a window of history between two numbers — bash 5.2 prints the number, a tab, then the line. Useful for grepping before you commit a command to a script, and `fc -ln -3` drops the numbers, which is the form you want when you paste the result in. fc is also how you edit and re-run a previous command in your editor.
$ $ export API_KEY=hunter2
$ history 2 1 history 2The leading space on the first line is the whole trick: with HISTCONTROL=ignorespace — or ignoreboth, which Ubuntu's default .bashrc sets — a line that begins with a space never reaches the history list, so the key is nowhere in ~/.bash_history. `history 2` shows only itself. Two related values finish the job: ignoredups drops a line that repeats the previous entry (two `echo dup` in a row leave one entry, verified here), and erasedups removes any earlier copy before saving the new one.
Common flags
- -c
- Clear the history list. The file is only rewritten when the shell exits, and only if histappend is off; with histappend set (Ubuntu's default .bashrc turns it on) the old lines stay in ~/.bash_history, so use `history -c && history -w` when you actually mean to scrub it.
- -d offset
- Delete the history entry at the given numbered position; a negative offset counts back from the end, so `history -d -1` drops the last command.
- -a
- Append the current-session lines to HISTFILE immediately, instead of waiting for exit
- -w
- Write the in-memory history to the file now
- -n
- Read new lines since last time from the file, without printing
- -r
- Read the file and load its lines into the current session's list
- -p
- Perform history expansion on args and print the result, without executing
History
The ! that predates bash
History expansion with `!` came from the C shell (csh), written by Bill Joy at Berkeley and first distributed with 2BSD in 1978 — history and command-line editing were its headline features, and `!!` and `!$` are its direct descendants. bash kept the whole family of bang shortcuts and layered readline's search keys on top: Ctrl-R for reverse incremental search, and Up-arrow prefix search once you bind it (see the tip below). Old-timers still argue over which is faster — the `!$` 'last argument' trick is straight csh lineage either way.
The history of history
Before `history`, Unix had no persistent memory of your commands — you retyped everything, every session. The interactive command history was one of the things that made Unix shells feel alive. bash has written ~/.bash_history from its first release in 1989, and the caps still decide how much survives: `man bash` sets HISTSIZE's default at 500 commands for the running session and gives HISTFILESIZE the same value by default. The 1000 / 2000 pair most people see is not bash's own setting but their distribution's — Ubuntu's /etc/skel/.bashrc ships `HISTSIZE=1000` and `HISTFILESIZE=2000`, which is why ~/.bashrc carries the same two lines.
Fun facts
Pros & cons
pros
- + Re-run any prior command with !<number> or !foo — no retyping long pipelines
- + Ctrl-R turns your entire memory of the terminal into a searchable, failure-proof index
- + !$ and !! let you chain onto the last command's output or escalate it with sudo in one keystroke
cons
- − Plaintext secrets and commands sit in ~/.bash_history unless you filter or scrub them
- − Each shell has its own in-memory copy; merging across sessions needs -a or setting histappend
- − Default dedup is off, so a repeated command can pad the file with thousands of near-identical lines
Takeaways
- 1Re-run without typing: `!42` by number, `!npm` by prefix, `!!` for the last command, `!$` for its last argument
- 2Ctrl-R is the habit that pays: press it, type a fragment, Enter to execute the match
- 3Protect secrets with a leading space and HISTCONTROL=ignorespace — or scrub the file before it bites
- 4Inspect before replaying: `fc -l 20 30` shows the numbered window you're about to reuse
- 5Persist across sessions reliably by exporting histappend and raising HISTFILESIZE
