History Expansion: Reusing Commands with !! and !$
What You'll Learn
- That history expansion is a "syntax for recalling past commands"
- How to reuse a command or argument instantly with
!!/!$ - How to pull specific items from history with
!string/!:n/:h:t:r - How
^old^new^quick substitution works, and how to stay safe withhistverify
sudo !! and cd !$. What is that ! mark?!, bash recalls a past command or argument so you can reuse it. It saves a lot of typing.1. What Is History Expansion?
Conclusion: History expansion is a bash feature that expands
!-syntax into a past command or argument, cutting typing and re-entry mistakes.
!! with the real command text. For example, !! expands to your entire previous command.History expansion is built from three kinds of parts.
| Part | Role | Example |
|---|---|---|
| Event designator | Which command to recall | !! / !42 |
| Word designator | Which word of that command to pull out | !$ / !^ |
| Modifier | How to transform the pulled-out word | :h / :t |
History expansion works mainly in the interactive bash shell. It is disabled by default inside scripts, so remember: "handy interactively, but don't write it in scripts."
2. Event Designators: Which Command to Recall
Conclusion:
!!is the previous command,!nis history number n,!stringis the latest command starting with string, and!?string?is the latest containing string.
!!, the previous command. It's the classic trick when you forgot sudo.!! — The Entire Previous Command
$ apt update E: Could not open lock file /var/lib/dpkg/lock-frontend ... $ sudo !! sudo apt update
!n / !-n — By History Number
$ history | grep tar 765 tar -czf backup.tar.gz /etc $ !765 tar -czf backup.tar.gz /etc
!n recalls history number n, and !-n recalls "n commands back" (!-1 is the same as !!).
!string — Latest Command Starting with string
$ !ssh ssh user@server
!string re-runs the most recent command that starts with string.
!?string? — Latest Command Containing string
$ !?server? ssh user@server
!?string? finds the most recent command that contains string anywhere, not just at the start. That is the difference from !string.
!string runs immediately with no confirmation. It can recall an older command you didn't intend, so when unsure, check the result first with :p (print only), covered next.
3. Word Designators: Pulling Out Arguments
Conclusion:
!$is the last argument,!^the first,!*all arguments, and!:nthe nth word. They save you from retyping long paths.
!$ is "the last argument of the previous command," so you avoid retyping a long path.!$ — The Last Argument
$ mkdir -p /var/log/myapp $ cd !$ cd /var/log/myapp
!^ and !* — First Argument / All Arguments
$ cp config.yml config.yml.bak $ vim !^ vim config.yml
!^ is the first argument, and !* is every argument except the command name.
!:n — The nth Word
Words are numbered from 0, where 0 is the command name itself.
$ cp src.txt dst.txt $ echo !:1 echo src.txt $ echo !:2 echo dst.txt
| Word designator | Meaning |
|---|---|
!^ |
First argument (same as !:1) |
!$ |
Last argument |
!* |
All arguments (!:1-$) |
!:0 |
The command name itself |
!:n |
The nth word |
!:n-m |
Words n through m |
!$ and friends implicitly target the previous command. To pull an argument from a different command, combine them with an event designator, like !ssh:$.
4. Modifiers: Transforming the Word
Conclusion:
:his the directory part,:tthe filename,:rstrips the extension,:ekeeps only the extension, and:pprints without running.
:h or :t after a word designator to split a path into pieces.$ ls /var/log/syslog $ echo !$:h echo /var/log $ echo !$:t echo syslog
| Modifier | Action | Example (/var/log/app.log) |
|---|---|---|
:h |
head: remove the trailing component (dir) | /var/log |
:t |
tail: keep only the trailing component | app.log |
:r |
root: remove the extension | /var/log/app |
:e |
extension: keep only the extension | log |
:p |
print: show the expansion only (no run) | — |
Use :p to Preview the Expansion
$ !ssh:p ssh user@server
With :p, history expansion prints the result without running it. The expansion is added to your history, so you can review it and then run it with !!.
When recalling commands with rm or sudo via !string, get into the habit of checking the result with :p first to avoid accidents.
5. Quick Substitution: ^old^new^
Conclusion:
^old^new^re-runs the previous command with the first old replaced by new. Perfect for fixing a typo.
^old^new^. It re-runs the previous command with old replaced by new. It's the go-to typo fix.$ cat /etc/hosst cat: /etc/hosst: No such file or directory $ ^hosst^hosts^ cat /etc/hosts
^old^new^ is shorthand for !!:s/old/new/ and replaces only the first match. To replace every match, use !!:gs/old/new/.
The trailing ^ is optional (^hosst^hosts also works). But if you want to append more text after the replacement, write the trailing ^ explicitly.
6. Staying Safe with histverify
Conclusion: With
shopt -s histverify, history expansion is not run immediately; it expands into the edit line so you can confirm before pressing Enter.
!! runs with no confirmation.histverify fixes that. With it set, the expanded result of !! appears in your input line first, and nothing runs until you press Enter.Add this one line to ~/.bashrc.
shopt -s histverify
Then reload with source ~/.bashrc. From now on, typing !! or !string shows the expanded command in the input line so you can review and edit it before running with Enter.
histverify is a safety net against running destructive commands by accident. If you use history expansion daily, turning it on gives peace of mind.
7. Summary
Conclusion: Combine the three parts — event designator, word designator, and modifier — to reuse past commands freely.
Here is a quick recap of the core history expansion syntax.
| Syntax | Meaning |
|---|---|
!! |
The entire previous command |
!n / !-n |
History number n / n commands back |
!string |
Latest command starting with string |
!$ / !^ |
Last argument / first argument |
!* |
All arguments |
!$:h :t |
Directory / filename of a path |
^old^new^ |
Re-run previous command, substituted |
!!:p |
Print the expansion only (no run) |
! gives so many ways to recall commands! I'll start with !! and !$.!string and ^old^new^, and your typing will get faster and faster.