The Lazy Typist's Guide to Shell History Expansion
Posted on Wed 09 September 2026 in Terminal
If you've spent any time in a terminal, you've probably typed a command, hit enter, watched it fail with "Permission denied," and then sighed as you retyped the whole thing with sudo stuck on the front. There's a better way, and it's been sitting in your shell this whole time.
The Classic: sudo !!
!! is bash and zsh's shorthand for "the last command I ran." Prefix it with sudo, and your shell expands it into the previous command with elevated privileges:
$ apt update
Reading package lists... Done
E: Could not open lock file /var/lib/dpkg/lock-frontend - Permission denied
$ sudo !!
sudo apt update
Most shells will echo the expanded command before running it, so there's no mystery about what's actually executing. It's a small trick, but once it's muscle memory, retyping a failed command feels almost old-fashioned.
The catch: !! re-runs the command exactly as you typed it, typos included. If you fat-fingered a flag or misspelled a package name, sudo !! will happily repeat the mistake with more permissions.
It Doesn't Stop at sudo
!! is just one piece of a much larger event-designator system that bash and zsh inherited from the original C shell. A few of the more useful ones:
!$— the last argument of the previous command. Handy when youmkdir some/long/nested/pathand immediately want tocd !$into it.!^— the first argument of the previous command.!*— all arguments of the previous command, useful for reusing a whole file list.!n— re-run command numbernfrom your history (checkhistoryfor the number).!string— re-run the most recent command starting withstring.!gitreruns your lastgit-something.!?string?— re-run the most recent command containingstringanywhere.^old^new— quick substitution: reruns the previous command witholdreplaced bynew. Great for fixing a single typo without retyping everything.
A quick example combining a couple of these:
$ touch report_final_v2.md
$ mv !$ report.md
mv report_final_v2.md report.md
The zsh Extras
zsh supports all of the !-expansions above natively — they're a Bourne/C-shell holdover, not bash-specific. But zsh also layers on its own history search that's worth knowing:
- Type a few characters and press Ctrl+R for reverse incremental search, same as bash.
- Type the start of a command and press the Up arrow — with the standard history bindings, this searches history for lines starting with what you've typed, not just cycling chronologically.
- The
sudoOh My Zsh plugin (if enabled) binds Esc twice to prependsudoto the current line — a nice complement tosudo !!, since it works even before you've run the command.
Why Bother
None of this is essential — you can always arrow-up and edit, or just retype. But history expansion shaves down the friction of the "run, fail, fix, rerun" loop that makes up a surprising share of a terminal session.
sudo !! is the gateway drug. !$, !*, and ^old^new are where it starts actually changing how you work.