Bash history: how to use !! and !$
!! dey repeat your last command, while !$ grabs its last argument. Learn six useful Bash history expansions, plus :p to print one before Bash runs am.
Wetin bash history expansion dey do
Bash history expansion dey rebuild command line wey you don run before from shell history before bash run am. !! dey repeat the previous command. !$ dey insert the last argument from the previous command. The substitution na text operation, and e dey happen first, before bash split the line into words. So wetin come out na the exact characters wey you type the first time.
Six forms cover almost everything wey you go do for server:
!!dey repeat the whole previous line, andsudo !!dey run am again as root.!$na the last argument from the previous line.!*na every argument from the previous line.!ndey run history entry number n, and!-ndey count backwards from where you dey now.!stringdey run the most recent command wey start withstring.^old^newdey run the previous line again, but e go replace the firstoldwithnew.
Everything for this section dey typed at interactive prompt for your own server. History expansion dey switched off for scripts. The last section explain this.
Path wey long, type am once
Na this kind case dey make the feature pay for itself. You dey prepare release directory, and the path long reach say if you type am again, typo fit enter easily.
sudo mkdir -p /srv/www/app/releases/2026-08-07
sudo chown -R deploy:deploy !$
ls -ld !$
sudo -u deploy nano !$/config.envBefore bash run each line, e go print the line wey the expansion produce:
sudo chown -R deploy:deploy /srv/www/app/releases/2026-08-07That echo na your check. Read am before you read the command output, because na only chance you get to see wetin bash decide !$ mean.
The chain dey work because bash dey store the expanded line for history, no be the !$ wey you type. So line 3 take im last argument from expanded line 2, and line 4 take am from line 3. Line 4 still show say text fit follow a designator: !$/config.env become the path with /config.env for the end, because the word designator stop for /.
One more example from the same session. After you edit the file, you wan list the directory wey hold am:
ls -l !$:h:h na the head modifier. E dey remove the last component from a path, the same work wey dirname dey do. :t keep only the last component, :r remove the extension, and :e keep only the extension.
Repeat the last command with !! and sudo !!
You forget sudo, and the service manager tell you so:
systemctl restart nginxFailed to restart nginx.service: Interactive authentication required.sudo !!Bash dey replace !! with the text of the previous line, so the shell go run sudo systemctl restart nginx.
!! na exactly the previous line, no matter wetin dey that line. Na here the classic mistake dey happen. You think say the last command na the one wey fail, but since then you don run a cd, or a history, or the failed command dey two lines back. sudo !! go then run the wrong command with root privileges. Print am first when you no sure:
sudo !!:p:p go print the expanded line but e no go run am. The printed line go enter your history, so if e correct, plain !! for the next line go run am.
Use !$ take the last argument again
!$ na the last word for the previous line. Na this form you go use pass, because the last word normally na the thing wey you dey work on: a path or a service name.
sudo systemctl status nginx
sudo systemctl reload !$Two things dey surprise people for here.
First, !$ na the last word, no be the last argument wey you get for mind. If redirection end the previous line, the last word na the redirect target:
sudo nginx -T > /tmp/nginx-dump.conf
less !$That one work well. But after journalctl -u nginx > /tmp/log.txt, !$ na /tmp/log.txt, no be nginx. Read the line wey e echo.
Second, the expansion na textual, so variable go come back without expansion. After ls $HOME/backups, !$ give you the characters $HOME/backups, then bash go expand am again like normal parameter. Remember this order: history expansion dey run before parameter expansion and before command substitution with $( ), so e no dey see values, na text only.
The forms wey dey near am good to know. !^ na the first argument, !:2 na the second, and !:2-4 na a range. !!:$ na the longer way to write !$.
If you prefer see the text before you commit to am, press Alt-. (or Esc and then .). Readline go put the last argument of the previous command directly inside your prompt, where you fit edit am. Press am again to move back to the last argument of the command before that one. Nothing go run until you press Enter.
Pass every argument on with !*
!* na every word for the previous line except the first one.
stat /srv/www/app/shared/config.env /srv/www/app/shared/secrets.env
sudo chmod 600 !*The phrase "except the first one" dey do important work. !* dey remove word 0 and nothing else, so options dey follow the paths. After ls -l file1 file2, !* na -l file1 file2, so sudo chmod 600 !* fail, because chmod dey receive -l as argument. Number the words from zero and use slice instead: !!:2* mean word 2 reach the end.
The same trap dey happen with sudo. After sudo chown deploy:deploy /srv/www/app, word 0 na sudo and word 1 na chown, so !* dey pass chown deploy:deploy /srv/www/app to the next command. Most times, na not wetin you want.
Pick command by position with !n and !-n
history dey print the list with number dey front of every entry.
history 5 512 sudo nginx -t
513 sudo systemctl reload nginx
514 ss -tulpn
515 sudo tail -f /var/log/nginx/error.log
516 history 5!513 dey run entry 513 again. !-2 dey run the entry two lines behind, count from the line wey you dey type now, so !-1 and !! mean the same thing.
Check the numbers immediately before you use dem. !-2 dey point to different place every time you run anything, including an ls wey you fire without thinking. Absolute numbers dey stable inside one session, but dem no be the same numbers for second session on the same box, and dem no remain the same after your next login read the history file back in. Number wey you memorise yesterday fit point to different command today.
Rerun command by prefix with !string
!string dey run the latest command wey start with string.
!ssThat one rerun ss -tulpn from the list above, the listening socket check wey dem describe for which ports dey open for Linux server. !?string? fit match anywhere for the line, instead of only the beginning. This one help when you remember argument but no remember command name.
Make the prefix long. !s go match ss, sudo, systemctl, or shutdown, whichever one run most recently. You no go know which one e be until e run. !string:p go print the match without running am. If nothing match, bash go print bash: !ss: event not found and run nothing. Na the safe outcome.
Fix one typo with ^old^new
sudo systemctl status ngnixUnit ngnix.service could not be found.^ngnix^nginxShell go run the previous line again, and e go replace the first ngnix with nginx. Na only the first match e dey change. To change every match, use the long form !!:gs/ngnix/nginx/, where s mean substitute and g mean make am across the whole line.
Print expansion before you run am
Two habits fit stop the accident where expansion go run something wey you no intend.
The first one na :p, wey you don already see. Add am to expansion and bash go print the result instead of running am: !!:p, or !systemctl:p. The printed line go enter your history, so !! afterwards go run the thing wey you just read.
The second one stronger, because e dey apply to every expansion without you needing remember anything:
shopt -s histverifyPut that line for ~/.bashrc. When histverify dey set, expansion no go run when you press Enter. Bash go write the expanded line back into your prompt so you fit read am and edit am, then press Enter a second time to run am. E cost one keystroke and e remove the whole class of accident. E need readline, so e dey work for interactive prompt only, and nowhere else.
Ctrl-R na the other way to work, and e never expand anything. Press Ctrl-R and type part of command. The prompt go change to reverse-i-search prompt and show the latest match as you dey type. Press Ctrl-R again to move back to older matches. Enter go run the line wey e dey show. Ctrl-G go cancel the search and return your original prompt. The left arrow key go end the search and leave the matched line for your prompt so you fit edit am.
Use Ctrl-R when you want see the command first. Use !! and !$ when the command na one line old and you still fit read am for screen.
Why echo "done!" dey talk say event no dey
echo "deploy done!"bash: !": event not foundHistory expansion dey happen before quoting resolve, and double quotes no dey protect the ! character. Na only single quotes and backslash fit do that. So echo 'deploy done!' go print wetin you expect. Backslash too dey stop the expansion, but inside double quotes bash go leave the backslash for the output, so single quotes na the clean solution.
This problem dey worse with passwords, because strong password often get !. Command like mysql -u app -p"S3cret!pass" fit fail with event-not-found error, or, if any history entry match am, e fit silently send another string. Use single quotes, or better, make the tool prompt you so the secret no go reach command line at all. This habit belong with the rest of your account hygiene: see the first ten minutes for new VPS to learn how to handle keys and passwords for fresh box.
If you wan paste text block wey full of ! characters, switch expansion off for the session with set +H, and switch am on again with set -H.
HISTSIZE, HISTFILESIZE and where dem dey keep your history
Two variables dey here, and people dey mix dem up because the names near alike.
HISTSIZEna how many commands the running shell go keep for memory.HISTFILESIZEna how many lines shell go keep for the file wey dey disk,~/.bash_historyunlessHISTFILEtalk otherwise.
Shell go write the file when e close, then e go cut the file to HISTFILESIZE lines at that time. Ubuntu default ~/.bashrc dey set 1000 and 2000. Check wetin your own really get:
grep HIST ~/.bashrc
echo "$HISTSIZE $HISTFILESIZE $HISTFILE"Increase both values if your !string searches dey miss commands wey you know say you run. Values like 10000 and 20000 dey common, and dem almost no cost anything because the file na plain text. Negative value mean say no limit dey.
Timestamps dey help when you come back to server after one week:
export HISTTIMEFORMAT='%F %T 'history go then print date and time before each entry, because bash go start write comment line wey hold epoch seconds before each command for the file.
The common complaint na say history dey disappear when you use more than one terminal. Each shell dey keep its own list for memory and write am out when e close, so without histappend, the last shell wey close go overwrite wetin the other shells save. Two settings go fix am:
shopt -s histappend
export PROMPT_COMMAND='history -a'histappend make shell add its list to the end of the file instead of replacing the file. history -a append new lines after every prompt, so even if session get killed instead of closing cleanly, e still leave the commands behind. Ubuntu default ~/.bashrc don set histappend already. This matter more as the number of machines wey you dey run increase, because history dey stored per user per machine. So when you dey manage several servers from one workstation, the !$ wey you want fit simply dey for another host.
How to keep secrets comot from your bash history
HISTCONTROL na e dey decide which lines go save at all.
ignorespacedey drop any line wey start with space.ignoredupsdey drop line wey exactly match the one before am.ignorebothdey do both things.erasedupsdey remove every older copy of the line wey e dey save.
export HISTCONTROL=ignorebothWhen ignorespace dey active, one space for front go keep command comot from the in-memory list, so e no go reach the file too. Check the value before you depend on am with echo "$HISTCONTROL". Ubuntu default user .bashrc dey set ignoreboth, but root shell or minimal image fit leave am unset. If that one happen, the space for front no go do anything, and the line go save like every other one.
Make you understand well wetin this protection cover. The line no go dey inside ~/.bash_history. But e still dey visible for ps output while the process dey run, so any other user for the machine fit read am there. sudo dey record the command wey e run for system log. The history file na plain text, so keep the file for mode 600. Also remember say anybody wey fit read your home directory fit read your last few thousand commands.
If sensitive information don already enter the list, remove the entry and write the file again:
history
history -d 517
history -whistory -d dey remove that entry from memory, and history -w dey write the current list over the file. history -c dey clear the whole list. HISTIGNORE na the related setting for noise instead of secrets: HISTIGNORE='ls:pwd:history:clear' dey keep those lines comot from the list, so your searches go return useful results.
Why history expansion dey do nothing inside shell script
History expansion na feature for interactive shell. Script dey run inside non-interactive shell, where history list no dey enabled and expansion dey off. So !! and !$ remain for the line as ordinary text. sudo !! inside script dey tell sudo make e run command wey name na literally !!, and e go fail.
Check any shell wey you dey use:
echo $-The output na the set of current option flags, something like himBHs. i mean say the shell dey interactive, while H mean say history expansion dey enabled. Run the same line inside script, and none of the two letters go dey there.
Na this one separate the two parts of your shell work. For prompt, !$ and Ctrl-R dey help you save keystrokes for commands wey you fit still see. For file, you go name things instead: put the path inside variable, or capture output with command substitution. Script wey depend on your personal history go behave differently for the next person wey run am. That one na opposite of wetin script suppose do.
Because of this, every example for this page na for you type at live prompt. None of dem go behave the same way if you paste dem inside a .sh file.
FAQ
Bash !! na wetin e dey do?
!! dey expand to the full text of the command line wey you run last, so sudo !! go run your last command again as root. The expansion na text, and e happen before bash parse the line. Bash go print the complete line just before e run am. If you no sure wetin the previous line be, type sudo !!:p first. :p go print the expansion without running am, and e go add the printed line to your history. So, following !! go run am.
How I fit use the last argument from the previous command again?
Use !$. After sudo mkdir -p /srv/www/app/releases, the line ls -ld !$ go become ls -ld /srv/www/app/releases. E dey take the last word for the line, so if redirection target dey for the end, e count as the last word. You fit still add text to am: !$/config.env go add text to the path, because the word designator stop for /. The interactive alternative na Alt-.. E go insert the same text for your prompt, so you fit read am before you press Enter.
Why bash dey talk say "event not found" when my text get exclamation mark?
Double quotes no protect ! from history expansion, so echo "done!" go make bash look for a history event and print bash: !": event not found. Single quotes protect am, so write echo 'done!'. Backslash still stop the expansion, but inside double quotes bash go leave the backslash for the output. To paste long block wey contain !, turn off expansion for the session with set +H.
Why !! and !$ no dey work for my shell script?
History expansion dey enabled only for interactive shells. Script dey run non-interactively, so shell no dey build history list, and !! go remain for the line as plain text. Run echo $- to see which one you dey use: interactive shell go print flags wey include i and H, while script no go print either one. For scripts, use variable or command substitution instead.
How I fit stop password from entering my bash history?
Set HISTCONTROL=ignorespace or HISTCONTROL=ignoreboth inside ~/.bashrc. Then start the command with one space, and e no go save am. Confirm the value with echo "$HISTCONTROL" first, because if e unset, the leading space no go do anything. This only keep the line out of ~/.bash_history. The command still dey visible for ps while e dey run, and sudo dey log wetin e execute. If secret don already save, find the number with history. Then run history -d <number> followed by history -w to rewrite the file.