Somewhere around my second month of using the Linux command line seriously, I stopped retyping long commands from scratch and started using shell history properly — and it genuinely changed how fast I worked. If you’re still mashing the up arrow one keystroke at a time to find a command you ran twenty minutes ago, this guide is going to save you a lot of time. I’ll cover everything from the basic history command up through reverse search, history expansion, and scripting-safe recall.
The history Command
history is a shell built-in (in bash, zsh, and most other common shells) that lists previously executed commands from the current session, numbered in order.
history
501 cd /var/log
502 tail -f syslog
503 grep ERROR syslog
504 systemctl status nginx
505 history
Because it’s a shell built-in and not a standalone binary, history only works inside an interactive shell session — you can’t run it from a script the same way, and running it in a non-interactive context (like a piped or background shell) typically returns nothing or an error, since there’s no interactive history buffer to report on.
Limiting Output
history 10
Shows only the last 10 entries — genuinely useful when your session has thousands of lines behind you and you just want the recent context.
Clearing History
history -c
Clears the in-memory history list for the current session (does not necessarily touch the history file on disk immediately — see below).
How Bash History Actually Works Under the Hood
Understanding this saves a lot of confusion later. Bash keeps two separate things:
- An in-memory history list for the current session, built up as you type commands.
- A history file on disk (by default
~/.bash_history), which is where history gets written when the shell exits (or via explicit commands), and read from when a new shell starts.
Key environment variables controlling this behavior:
echo $HISTSIZE
echo $HISTFILE
echo $HISTFILESIZE
On a typical system:
1000
/root/.bash_history
2000
HISTSIZE— maximum number of commands kept in memory for the current session.HISTFILE— path to the history file on disk (default~/.bash_history).HISTFILESIZE— maximum number of lines kept in the history file on disk (can differ fromHISTSIZE).
This split explains a common point of confusion: if you have multiple terminal tabs open simultaneously, each has its own in-memory history, and by default, whichever shell exits last simply overwrites ~/.bash_history with its own list — so commands from an earlier-closed tab can appear to vanish. This is fixable (see the histappend shopt option below).
Useful History-Related Shell Options
shopt -s histappend
Appends to the history file instead of overwriting it when the shell exits — I consider this close to mandatory for anyone regularly using multiple terminal sessions.
HISTCONTROL=ignoredups:erasedups
Prevents consecutive duplicate commands from cluttering history, and removes older duplicates of a command when it’s run again.
HISTIGNORE="ls:cd:pwd:clear"
Excludes trivial, high-frequency commands from being recorded at all.
HISTTIMEFORMAT="%F %T "
Adds a real timestamp to each history entry, which by default has no time information at all — genuinely important for auditing, since without it you know a command ran, but not when.
These typically go in ~/.bashrc for a per-user setup, or a file under /etc/profile.d/ for a system-wide default.
Recalling and Re-Running Commands
The Up/Down Arrow Keys
The simplest method: pressing the Up arrow steps backward through history one command at a time; Down steps forward. This is what most beginners start with, and it’s fine for recent commands, but it doesn’t scale to searching through hundreds of lines of history.
Reverse Incremental Search (Ctrl+R)
This is the single most valuable shell shortcut I use daily. Press Ctrl+R, then start typing any part of a command you remember:
(reverse-i-search)`nginx': systemctl restart nginx
As you type more characters, bash narrows the match live. Press Ctrl+R again to cycle to the next older match containing that text. Press Enter to execute the found command, or Esc (or the right arrow) to drop it into the prompt for editing without running it yet.
The ! (Bang) History Expansion Syntax
This is classic Unix shell history expansion, and while it’s a bit cryptic at first, it’s extremely fast once memorized:
| Syntax | Effect |
|---|---|
!! | Re-run the previous command |
!n | Run history entry number n (from history output) |
!-n | Run the command n commands back from the current one |
!string | Run the most recent command starting with string |
!?string | Run the most recent command containing string anywhere |
!$ | The last argument of the previous command |
!* | All arguments of the previous command |
^old^new | Re-run the previous command, replacing old with new |
A few of these come up constantly in real workflows:
sudo !!
Genuinely one of the most-used shortcuts in Linux — you run a command, get a “permission denied,” and instead of retyping the whole thing, you just prefix it with sudo.
mkdir /var/log/myapp
cd !$
!$ expands to /var/log/myapp, the last argument of the previous command — handy any time you just created or referenced a path and want to immediately act on the same one.
ls /etc/nginx/sites-available
^available^enabled
Quickly re-runs the previous command with one substring swapped, without retyping the whole line.
Editing a Recalled Command Before Running It
Bash supports a proper command-line editor mode based on either emacs or vi keybindings (controlled by set -o vi or set -o emacs, with emacs-style being default). In default emacs-style mode, once a command is on your prompt line (recalled via Ctrl+R, arrow keys, or ! expansion), you can navigate and edit it with shortcuts like Ctrl+A (start of line), Ctrl+E (end of line), Ctrl+W (delete previous word), and Alt+. (insert the last argument of the previous command, similar to !$ but interactively).
Practical Sysadmin Examples
Auditing what a user actually ran, with timestamps:
HISTTIMEFORMAT="%F %T " history | tail -50
Finding every time a specific command was run in this session:
history | grep systemctl
Re-running the last command as a background job:
!! &
Building a quick one-off script from recent commands:
history | tail -20 | cut -c 8- > recent_commands.sh
This strips the numbering column off history output and dumps the raw commands into a file — a fast way to turn an ad hoc troubleshooting session into a reusable script.
Persisting history immediately instead of waiting for shell exit:
history -a
Appends the current session’s new history entries to HISTFILE immediately, without needing to close the shell — useful before a risky operation, so your command trail is safely on disk even if the session crashes afterward.
Security Implications
Shell history is a genuine security consideration, not just a convenience feature:
- Sensitive data leakage: typing a password or API token directly on the command line (e.g.,
mysql -u root -pMySecretPassword) writes it straight into~/.bash_history, plain text, readable by anyone with access to that file or that user account. Prefer prompted input, environment variables sourced from a protected file, or config files with restricted permissions instead. - Preventing a single command from being recorded: prefixing a command with a leading space, combined with
HISTCONTROLincludingignorespace, tells bash to skip recording that one line — genuinely useful for a one-off command containing a secret.
HISTCONTROL=ignorespace:ignoredups
curl -H "Authorization: Bearer supersecrettoken" https://api.example.com
Note the leading space before curl — with ignorespace set, this line won’t be saved to history.
- File permissions:
~/.bash_historyshould never be world-readable. Default permissions (600, owner read/write only) are correct; verify withls -l ~/.bash_historyif you’re auditing a shared or hardened system. - Auditing/compliance environments: some hardened systems intentionally centralize and forward shell history to a remote logging system for accountability, since local history is trivially clearable by any user (
history -c, or deleting the file outright) and shouldn’t be relied on as tamper-proof audit evidence on its own.
Troubleshooting
- History seems to vanish between sessions → likely missing
shopt -s histappend, causing the last-closed shell to overwrite the file instead of merging. Ctrl+Rsearch isn’t working → confirm you’re in emacs-mode keybindings (set -o emacs, the default); ifset -o viis active, reverse search behaves differently (usually/in command mode instead).historyshows nothing at all → you may be in a non-interactive shell (like inside a script or a piped subshell), where interactive history simply isn’t tracked the same way.- Duplicate commands cluttering output → set
HISTCONTROL=ignoredups:erasedupsin~/.bashrc.
History Behavior in zsh, for Comparison
If you split time between bash and zsh (increasingly common, since zsh is the default interactive shell on modern macOS and popular among Linux users who adopt frameworks like Oh My Zsh), it’s worth knowing where the history model diverges, since assuming identical behavior across both can lead to real surprises.
zsh uses HISTFILE, HISTSIZE, and SAVEHIST (a zsh-specific variable controlling how many lines are saved to disk, distinct from bash‘s HISTFILESIZE), and by default in many popular configurations, shares history live across multiple simultaneously open terminal sessions in near real-time, rather than only writing to disk on shell exit. This is controlled by options like SHARE_HISTORY and INC_APPEND_HISTORY, which many zsh configuration frameworks enable by default — meaning a command run in one open terminal tab can appear in another tab’s history immediately, without either shell needing to close first. This is a meaningfully different experience from stock bash, where (absent histappend and careful manual syncing) each session’s history is more isolated until it exits.
Ctrl+R reverse search works essentially the same way in both shells by default, though zsh users frequently layer additional fuzzy-history tools on top, such as fzf‘s shell integration, which replaces the built-in Ctrl+R with a fuzzy-matching, multi-result picker — genuinely worth trying if you find yourself doing heavy history searching regularly, since it shows multiple candidate matches at once instead of cycling through them one at a time.
A Few More Advanced bash History Tricks
Recalling and modifying the Nth-to-last command’s arguments:
!!:1 # first argument of the previous command
!!:2 # second argument of the previous command
!:2-3 # arguments 2 through 3 of the previous command
Recalling a command from several steps back and running it with a new first word:
!42:s/old/new/
Applies a substitution to history entry 42 before executing it, similar to ^old^new but targeting an arbitrary history entry rather than just the immediately preceding command.
Printing what a history expansion would do without executing it:
shopt -s histverify
With this option set, expansions like !! or !42 are loaded onto the prompt line for review and editing, rather than executing immediately — a genuinely good safety habit to adopt if you’ve ever accidentally re-run a destructive command via a mistyped ! expansion, since it adds a manual confirmation step before anything actually runs.
Compatibility Across Shells and Distributions
The history built-in and !-based expansion are common to bash and largely mirrored (with some syntax differences) in zsh, tcsh, and ksh. Ctrl+R reverse search works the same way across bash and zsh by default. dash (used as /bin/sh on Debian/Ubuntu for scripts) has no interactive history support at all, since it’s designed as a minimal POSIX-compliant script interpreter, not an interactive shell — this only matters if you’re specifically dropped into sh rather than bash. Across distributions, the defaults for HISTSIZE/HISTFILESIZE can differ slightly (some ship with much larger defaults, like 10,000+), so it’s worth checking your own ~/.bashrc rather than assuming.
Summary
Shell history recall is one of those unglamorous skills that quietly saves enormous amounts of time once it’s second nature. Ctrl+R for fuzzy searching, !!/!$/sudo !! for fast repeats, and a well-configured HISTCONTROL/HISTTIMEFORMAT setup in ~/.bashrc together turn a raw command log into a genuinely powerful tool — while understanding the security implications keeps that convenience from turning into an accidental credential leak.
References
man 1 bash(see the HISTORY and HISTORY EXPANSION sections)- GNU Bash Reference Manual: https://www.gnu.org/software/bash/manual/bash.html
man 3 readline(for command-line editing keybindings)
