Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Bash & the POSIX Shell  Module 13 – The interactive shell

History, Ctrl-R and readline editing

Reuse commands with Ctrl-R and history designators, set history size, duplicates and timestamps, and edit the command line with readline in emacs or vi mode.

  • Beginner
  • 5 minutes
  • Examples run with Bash 5.3.20, dash 0.5.13.5, shellcheck 0.11.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 5 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Reuse past commands with Ctrl-R, !!, !$ and the history list
  • Configure history size, duplicate handling and timestamps
  • Edit the command line efficiently with readline keys and an inputrc

Before you start

What you’ll learn · Before you start

What you will learn

  • Reuse past commands with Ctrl-R, !!, !$ and the history list
  • Configure history size, duplicate handling and timestamps
  • Edit the command line efficiently with readline keys and an inputrc

Before you start

On this page

You ran a 90-character rsync command ten minutes ago and need it again. Retyping it invites a typo; scrolling up through dozens of lines is slow. Bash keeps a history list of every command, and a few keystrokes bring any of them back exactly.

Here you learn to search and reuse history with Ctrl-R and the history designators !!, !$ and !n, to set how much history is kept and what is left out of it, and to move around a command line with readline, the line editor built into Bash.

Reuse commands with Ctrl-R and history expansion

Press Ctrl-R and type a few letters: Bash searches your history list: The numbered list of commands Bash keeps for the session and saves to the history file; browse it with the history builtin and search it with Ctrl-R. backwards and shows the most recent command that contains them, which you run with Enter or edit first. For quick reuse without searching, Bash offers history expansion: Shorthand starting with ! (such as !!, !$ and !n) that Bash replaces with a past command or part of one before running the line; interactive shells only., words starting with ! that the shell replaces before running the line. This example feeds a sequence to an interactive Bash and prints what each designator did:

History designators acting on the previous command Python · history_expansion_session.py
from session import run

# History expansion in an interactive Bash reading a file of commands: !! repeats the last command, ^old^new fixes a
# typo in it, !$ is its last word and !1 re-runs entry 1. The history list stores each command in its expanded form.
run("""
printf '%s\\n' 'echo alpha beta gamma' '^beta^BETA' '!!' 'echo "last arg: !$"' '!1' 'history 6' > demo.bash
bash --norc --noprofile -i demo.bash 2>/dev/null
""")

Output

$ printf '%s\n' 'echo alpha beta gamma' '^beta^BETA' '!!' 'echo "last arg: !$"' '!1' 'history 6' > demo.bash
$ bash --norc --noprofile -i demo.bash 2>/dev/null
alpha beta gamma
alpha BETA gamma
alpha BETA gamma
last arg: gamma
alpha beta gamma
    1  echo alpha beta gamma
    2  echo alpha BETA gamma
    3  echo alpha BETA gamma
    4  echo "last arg: gamma"
    5  echo alpha beta gamma
    6  history 6

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 history_expansion_session.py

echo alpha beta gamma ran first. ^beta^BETA re-ran it with the first beta changed to BETA, printing alpha BETA gamma. !! repeated that whole command, !$ stood for its last word (gamma), so echo "last arg: !$" printed last arg: gamma, and !1 re-ran entry 1 by its number. The history list at the end shows each command stored in its expanded form.

Interactive · stage by stage What each history designator picks Step through the designators in order and watch each one act on the command before it.
What each history designator picks, step 5 of 5. Showing Run a command, ^beta^BETA fixes it, !! repeats the last, !$ is the last word, !1 re-runs entry 1. In focus: !1 re-runs entry 1.Run a commandecho alpha beta gamma^beta^BETA fixes itecho alpha BETA gamma!! repeats the lastruns it again!$ is the last wordgamma!1 re-runs entry 1by its number

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Type a command. You run echo alpha beta gamma. It goes into the history list as entry 1, and becomes the "last command" for the next designator.
  2. Fix a typo in the last command. ^beta^BETA re-runs the previous command with the first beta changed to BETA. It printed alpha BETA gamma, so a quick typo fix needs no retyping.
  3. Repeat the last command. !! stands for the whole previous command, now echo alpha BETA gamma, and runs it again. Common as sudo !! to repeat a command with sudo.
  4. Reuse its last word. !$ is the last word of the previous command, here gamma, so echo "last arg: !$" printed last arg: gamma. !^ is the first argument.
  5. Run an entry by number. !1 re-runs history entry 1, echo alpha beta gamma. history shows the numbers, and !-2 counts back from the current line.

A few designators cover most use: !! the last command (often sudo !!), !$ its last argument, !^ its first, !n entry number n, !-2 two back, and !abc the most recent command starting with abc. History expansion works only in interactive shells, so a script is never surprised by a !.

An exclamation mark in double quotes

In an interactive shell, echo "it works!" can fail, because !" starts a history expansion. Use single quotes, echo 'it works!', or a backslash. Scripts are not affected, as history expansion is off there.

Outside that one quoting trap, the designators are safe and save a lot of typing once they are habit.

Key idea

Ctrl-R searches history as you type, and the ! designators (!!, !$, !n) pull a past command or one of its words into the current line before it runs.

Checkpoint

Check: Ctrl-R and history designators

  1. Question 1 of 2 You ran a command, then type !$. What does !$ stand for?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The last word of the previous command

    !$ expands to the last argument of the previous command, so mkdir dir && cd !$ changes into the new directory.

  2. Question 2 of 2 What does pressing Ctrl-R at the prompt do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Searches the history list backwards for what you type

    Ctrl-R shows the most recent command containing your letters, which you run with Enter or edit first.

Shape your history: size, duplicates and timestamps

By default the history file can fill with near-duplicates and secrets. A few variables, set in ~/.bashrc, shape what is kept. The most useful is HISTCONTROL=ignoreboth, which drops a command that repeats the one before it and any command that begins with a space:

HISTCONTROL=ignoreboth skips duplicates and spaced commands Python · history_control_session.py
from session import run

# HISTCONTROL=ignoreboth drops a command that repeats the one before it and a command that starts with a space. Both
# commands still run; they just do not go into the history list. The leading space is the way to keep a secret out.
run("""
printf '%s\\n' 'HISTCONTROL=ignoreboth' 'echo build' 'echo build' ' echo token=secret' 'echo deploy' 'history 5' > ctrl.bash
bash --norc --noprofile -i ctrl.bash 2>/dev/null
""")

Output

$ printf '%s\n' 'HISTCONTROL=ignoreboth' 'echo build' 'echo build' ' echo token=secret' 'echo deploy' 'history 5' > ctrl.bash
$ bash --norc --noprofile -i ctrl.bash 2>/dev/null
build
build
token=secret
deploy
    1  HISTCONTROL=ignoreboth
    2  echo build
    3  echo deploy
    4  history 5

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 history_control_session.py

Both echo build lines ran, but the repeat was not stored, so the history list holds echo build once. The space-prefixed echo token=secret ran and printed its output, yet never entered the list: starting a command with a space is the usual way to keep a one-off secret out of history. echo deploy was stored normally.

Setting (in ~/.bashrc) What it does
HISTSIZE=5000 how many commands are kept in memory for this session
HISTFILESIZE=5000 how many lines are kept in the history file on disk
HISTCONTROL=ignoreboth skip duplicates and commands that start with a space
HISTTIMEFORMAT='%F %T ' show a date and time before each entry in history
shopt -s histappend append to the history file on exit instead of overwriting it

shopt -s histappend matters when you keep several terminals open: without it, the last one to close overwrites the file with only its own history, losing the rest. With it, each shell appends, so no session’s commands are thrown away.

Checkpoint

Check: Shaping the history

  1. Question 1 of 2 With HISTCONTROL=ignoreboth, you run " echo token=secret" (one leading space). What happens?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It runs, but is not saved in the history list

    In the session the spaced echo printed token=secret but never appeared in history 5.

  2. Question 2 of 2 You keep four terminals open and lose most of your history when they close. Which setting helps?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: shopt -s histappend

    Without histappend, the last shell to close overwrites the file with only its own history.

Edit the line with readline

Everything you type at the prompt is handled by readline: The line-editing library inside Bash that handles typing at the prompt, key bindings and history search; configured in ~/.inputrc and shared with other programs., the line editor inside Bash. Its default keys come from the Emacs editor, so moving and deleting is fast once the keys are habit:

Key What it does
Ctrl-A / Ctrl-E jump to the start / end of the line
Alt-B / Alt-F move back / forward one word
Ctrl-W / Ctrl-U delete the word before the cursor / to the start of the line
Ctrl-K delete from the cursor to the end of the line
Ctrl-Y paste back what you last deleted
Ctrl-R search history as you type

Prefer vi keys? set -o vi switches readline to a vi-style mode, with a command mode reached by Escape. Readline is configured in ~/.inputrc: for example, set completion-ignore-case on makes filename completion ignore case, and you can bind keys to actions such as searching history with the up and down arrows. Because readline is a separate library, these settings also apply to other programs that use it, such as python3 and psql.

Set the mode where every shell sees it

Put set -o vi (or your readline set lines) in ~/.bashrc so every interactive shell starts the same way. Changes to ~/.inputrc take effect in new shells, or at once with Ctrl-X then Ctrl-R, which re-reads the file.

Checkpoint

Check: Readline editing

  1. Question 1 of 2 Which readline key jumps the cursor to the start of the line?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Ctrl-A

    The default readline keys come from Emacs: Ctrl-A start, Ctrl-E end, Alt-B and Alt-F by word.

  2. Question 2 of 2 Where do you put "set completion-ignore-case on" so readline uses it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: In ~/.inputrc

    ~/.inputrc holds readline set and bind lines; new shells read it, or Ctrl-X Ctrl-R re-reads it.

Practise: history and readline

Each question shows a history list, a setting or a readline key like the ones above. Decide what happens, then check your answer:

Practice that adapts to you

Practise this lesson

7 questions about 3 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 A command failed because it needed root. What is the quickest way to repeat it with sudo?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: sudo !!

    !! is the previous command; sudo !! is the classic way to retry it with elevated rights.

  2. Question 2 of 7 Your last command was "grep eror app.log". What does ^eror^error print it as?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: It re-runs the command as grep error app.log

    In the session ^beta^BETA turned echo alpha beta gamma into echo alpha BETA gamma.

  3. Question 3 of 7 A script contains echo "done!". Does history expansion change it when the script runs?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: No, history expansion is off in non-interactive shells

    History expansion works only in interactive shells, so an ! in a script is just text.

  4. Question 4 of 7 Which value of HISTCONTROL skips both duplicate commands and ones that start with a space?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: ignoreboth

    ignoreboth is ignoredups plus ignorespace, as the session showed.

  5. Question 5 of 7 What is the difference between HISTSIZE and HISTFILESIZE?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: HISTSIZE is commands kept in memory this session; HISTFILESIZE is lines kept in the file on disk

    HISTSIZE bounds the session's list; HISTFILESIZE bounds the ~/.bash_history file.

  6. Question 6 of 7 Which line switches readline to vi-style editing for new interactive shells?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: set -o vi in ~/.bashrc

    set -o vi switches to vi keys; the default Emacs keys return with set -o emacs.

  7. Question 7 of 7 Why does a setting in ~/.inputrc also change how python3 edits its input prompt?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: Both Bash and python3 use the readline library, which reads ~/.inputrc

    readline is separate from Bash, so its ~/.inputrc settings apply across programs that use it.

Interview questions

Warm-up (fresher to 2–5 years): how do you rerun your last command, and reuse just its last argument?

!! expands to the whole previous command, so sudo !! repeats it with sudo in front. !$ is the last word of the previous command, handy as mkdir reports && cd !$. Both are history expansion, done by the shell before the line runs, and both work only in an interactive shell. To find an older command, press Ctrl-R and type part of it; Bash shows the most recent match, which you run with Enter or edit first. The history list is numbered, so !n re-runs entry n, and history shows the numbers.

Two harder questions follow, about a history file that keeps losing commands and about a password that ended up in the history. Decide what you would check first, then open each first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A developer keeps several terminals open all day, and by evening most of the morning's commands are missing from history. Each terminal seemed fine on its own. What is happening, and how do you fix it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A code review finds an API token in a developer's shell history, pasted once as an argument to a command. Explain how to remove it and how to keep secrets out of history in future.

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How do you find and rerun an older command at the prompt?

    Press Ctrl-R and type part of it: Bash shows the most recent matching command as you type, and Enter runs it.

  2. What do !!, !$ and !n expand to?

    !! is the whole previous command, so sudo !! repeats it with sudo; !$ is its last word, as in mkdir reports && cd !$; and !n is command number n of the history list.

  3. Where does history expansion with ! happen?

    Only in interactive shells, before the line runs. An ! in a script is harmless, but at the prompt an ! inside double quotes needs care.

  4. What do HISTCONTROL=ignoreboth and shopt -s histappend change?

    ignoreboth keeps out of the history a command that repeats the one before it and every command that starts with a space, and histappend makes each terminal append to the history file instead of overwriting the others.

  5. Which keys does readline use to edit the command line, and where is it configured?

    Emacs keys by default, or vi keys after set -o vi. ~/.inputrc configures readline for Bash and every other program that uses the library.

Key takeaways

  • Ctrl-R searches the history list as you type, and !!, !$ and !n pull a past command or a word of it into the current line before it runs.
  • History expansion works only in interactive shells, so an ! in a script is harmless but an ! in double quotes at the prompt needs care.
  • HISTCONTROL=ignoreboth skips duplicates and space-prefixed commands, and shopt -s histappend keeps several terminals from overwriting each other’s history.
  • Readline edits the line with Emacs keys by default (set -o vi for vi keys), and ~/.inputrc configures it for Bash and every other program that uses readline.

Terms in this lesson

history expansion
Shorthand starting with ! (such as !!, !$ and !n) that Bash replaces with a past command or part of one before running the line; interactive shells only.
history list
The numbered list of commands Bash keeps for the session and saves to the history file; browse it with the history builtin and search it with Ctrl-R.
readline
The line-editing library inside Bash that handles typing at the prompt, key bindings and history search; configured in ~/.inputrc and shared with other programs.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.