skip to content

You keep two bash terminals open all day, and commands typed in the first are missing from history after the second one exits. How does bash write its history file, and which settings stop concurrent sessions from clobbering each other?

level: middleimportance: should knowfreq 52%

answer

  1. history lives in memory until exit
  2. default write replaces, not appends
  3. one shopt option fixes the clobbering
  4. memory size versus file size
  5. per-prompt append for live sharing

basics

~20 s

Each bash session keeps history in memory and writes it out when the shell exits, overwriting HISTFILE by default — so the last shell to exit wins. shopt -s histappend makes sessions append instead, and a history -a in PROMPT_COMMAND shares lines immediately.

solid answer

~50 s

An interactive bash reads `HISTFILE` (default `~/.bash_history`) into an in-memory list at startup and does not touch the file again until the shell exits. On exit it writes that in-memory list back, and by default it **replaces** the file rather than appending — so if two shells started from the same snapshot and each added its own commands, whichever exits last overwrites the other's additions. `shopt -s histappend` changes the write to an append, and is the single most important setting here. `HISTSIZE` bounds the in-memory list while `HISTFILESIZE` truncates the file, so setting either too small discards history no matter what else you configure. If you want lines visible across sessions immediately rather than at exit, add `history -a` (append this session's new lines now) and `history -n` (read lines other sessions appended) to `PROMPT_COMMAND`.

code

bash · 7 lines
bash
# ~/.bashrc — history that survives many concurrent terminals
shopt -s histappend
HISTSIZE=100000
HISTFILESIZE=200000
HISTCONTROL=ignoreboth
HISTTIMEFORMAT='%F %T '
PROMPT_COMMAND="history -a; history -n${PROMPT_COMMAND:+; $PROMPT_COMMAND}"

go deeper

for a junior

Know that history is per-session and saved when the shell exits, that Ctrl-R searches it, and that ~/.bash_history is the default file. Be able to name HISTSIZE.

for a middle

Explain the in-memory-list model and the default overwrite-on-exit that causes clobbering, then name shopt -s histappend, the HISTSIZE/HISTFILESIZE split, and the HISTCONTROL values by their exact spellings.

for a senior

Show the live-sharing setup with history -a and history -n in PROMPT_COMMAND and its tradeoffs, and reason about failure modes: killed shells, root-owned history files, huge files slowing every shell start.

for a principal

Own the policy angle: shell history is not an audit trail — it is user-editable, per-user and trivially disabled. Decide where real command auditing comes from and how to keep secrets off command lines in the first place.

## The model: memory now, file later Bash's history is a per-process, in-memory list. At startup an interactive shell reads `$HISTFILE` (defaulting to `~/.bash_history`) into that list. Everything you type is added to the list, and everything `Ctrl-R` or the up-arrow searches comes from the list — not from the file. The file is touched again only when the shell exits, or when you explicitly ask via the `history` builtin. That single design fact explains the symptom. Two shells opened at 09:00 both loaded the same snapshot. Each has been appending to its own private list. When shell A exits it writes *its* list to the file. When shell B exits it writes *its* list — and with the default `histappend` off, that write truncates the file first. B's lines survive; A's are gone. ## The settings that matter ```bash shopt -s histappend # append on exit instead of overwriting HISTSIZE=100000 # lines kept in memory for this session HISTFILESIZE=200000 # lines kept in the file after truncation HISTCONTROL=ignoreboth # ignorespace + ignoredups HISTIGNORE='ls:bg:fg:history' # patterns never recorded at all HISTTIMEFORMAT='%F %T ' # store and display timestamps ``` - **`shopt -s histappend`** is the fix for the clobbering itself. It belongs in `~/.bashrc`, because it is per-shell state. - **`HISTSIZE` vs `HISTFILESIZE`** is the classic confusion: the first bounds the in-memory list, the second truncates the file after it is written. Appending faithfully into a file capped at 500 lines still leaves you with 500 lines. In bash 4.3 and later, a value of `-1` or an empty value means unlimited. - **`HISTCONTROL`** takes `ignorespace` (a command starting with a space is not recorded), `ignoredups` (consecutive duplicates collapse), `ignoreboth` (both), and `erasedups` (remove all previous copies of the line). `ignorespace` is the habit people use to keep a one-off secret out of the file — a convenience, not a security control: the value still appears in the process table and in whatever the command wrote. - **`HISTIGNORE`** is a colon-separated pattern list for commands never worth recording. ## Sharing history immediately If you want a command typed in one terminal to be findable in another a second later, do the file work on every prompt instead of at exit. `PROMPT_COMMAND` holds a command bash runs before printing each primary prompt: ```bash shopt -s histappend PROMPT_COMMAND='history -a; history -n' ``` - `history -a` appends the lines added in *this* session since the last append to the file. - `history -n` reads lines appended to the file by *other* sessions into this session's list. - The stricter variant `history -a; history -c; history -r` clears the in-memory list and re-reads the whole file, giving perfectly merged history at the cost of interleaving every shell's commands into your up-arrow — most people find that worse, not better. Be aware `PROMPT_COMMAND` is a shared hook: append to it rather than assigning over it if other tooling already uses it. ## Searching what you have `Ctrl-R` runs an incremental reverse search through the in-memory list; `history` prints it with line numbers; `!123` re-runs a numbered entry and `!!` the previous one. Because all of these read the in-memory list, none of them will show another session's commands until that session's lines have been appended and read back in. ## Failure modes to expect - **A shell killed with `SIGKILL`, or a machine that loses power, writes nothing** — that whole session's history is lost. Per-prompt `history -a` is the mitigation. - **`HISTFILE` unset** in a startup file disables saving entirely; some hardened profiles do this deliberately. - **A read-only or root-owned `~/.bash_history`** (a classic result of running a shell under `sudo` without `-i`) makes every write fail. - **Huge history files** slow startup, because the whole file is parsed into memory at every shell start. ## What to put in ~/.bashrc A reasonable baseline is `shopt -s histappend`, generous `HISTSIZE`/`HISTFILESIZE`, `HISTCONTROL=ignoreboth`, `HISTTIMEFORMAT` for timestamps, and per-prompt appending if you live in many terminals at once.

  • What is the difference between HISTSIZE and HISTFILESIZE?
    `HISTSIZE` caps the number of commands kept in the shell's in-memory list for the current session. `HISTFILESIZE` caps the file: after history is written, bash truncates `$HISTFILE` to that many lines. A large `HISTSIZE` with a small `HISTFILESIZE` still loses history across sessions, which is why the two are normally set together.
  • A colleague prefixes a command with a space to keep a password out of history. Is that a sound security practice?
    It works only if `HISTCONTROL` includes `ignorespace`, and even then it addresses one file. The secret is still visible in the process table while the command runs, may sit in terminal scrollback, and is often logged by the tool itself or by auditd. Treat it as tidiness, not a control — pass secrets through environment variables, restrictive-mode files, or stdin.
  • Why might history from a shell you closed by killing the terminal be missing entirely?
    Bash writes the history file when the shell exits. A window closed in a way that delivers SIGHUP usually still lets bash save, but a hard SIGKILL or a machine crash gives it no chance and the whole in-memory list is lost. Appending on every prompt with `history -a` in `PROMPT_COMMAND` bounds that loss to the current command.

saying these in an interview costs you the question

  • Thinks each command is written to the history file immediately
  • Says HISTSIZE controls how many lines the file keeps
  • Believes histappend is on by default
  • Treats a leading space as a real way to protect secrets
  • Expects Ctrl-R to find commands typed in another open terminal

context