Most Linux users come across the ! symbol in only two situations: when they learn the sudo !! shortcut, or when a password containing an exclamation mark inside double quotes causes the event not found error.
Everything else that ! can do often goes unnoticed for years.
The reason is that ! actually has two completely different purposes in Bash.
In an interactive terminal, it is used for history expansion, which lets you quickly recall, reuse, and edit commands you’ve already run.
In tests, patterns, and parameter expansion, ! is used for logical negation, and this works in both interactive shells and shell scripts.
Since the same symbol serves two different roles, it’s easy for beginners to get confused.
In this guide, you’ll learn 10 practical uses of the ! character in Bash, from recalling and modifying previous commands to using logical negation in shell scripts and exclusion patterns.
All examples were tested with Bash 5.2 and work on modern Linux distributions.
While some history expansion features are also available in shells such as zsh and ksh (KornShell), but shells such as dash and POSIX sh do not support history expansion at all.
This difference becomes especially important when you start writing shell scripts.
Every command you execute in Bash is assigned an event or unique number in your command history.
If you know the event number, you can rerun that command instantly without typing it again.
First, display your command history:
Example output:
The output shows a numbered list of previously executed commands.
To rerun one of them, type ! immediately followed by its event number:
!1551
Bash expands !1551 into the command stored under event number 1551 and executes it immediately.
which in this example is:
The actual event numbers on your system will be different, so use the history command to find the correct one before rerunning it.
You don’t always need to remember a command’s event number.
Bash also lets you reference commands by their position relative to the current command, which is handy when you only need to rerun something from the last few commands.
The most recently executed command is !-1, the command before that is !-2, then !-3, and so on.
For example:
In this example:
Unlike absolute event numbers (such as !1551), relative references change every time you execute another command because they’re always counted from your current position in the history list.
If you’re unsure which command a relative reference points to, you can preview it before running it using the :p modifier, which you’ll learn later in this guide.
Relative references are ideal for quickly repeating one of your recent commands.
However, if you need to rerun the same command later in the session, using its event number (for example, !1551) is usually more reliable because that event number remains unchanged for the duration of the current shell session.
One of the biggest time-savers in Bash is reusing arguments from the command you just ran.
Instead of typing long file or directory paths again, you can recall them with history expansion word designators.
For example, suppose you list a directory:
A moment later, you realize you wanted the long listing format.
Rather than retyping the entire path, use !$ to insert the last argument from the previous command:
Before executing the command, Bash expands !$ to:
Common Word Designators: The following history expansion shortcuts all refer to the previous command:
For example, if you create several directories:
You can immediately reuse the same list of directories with another command:
This saves you from retyping long argument lists and reduces the chance of typing mistakes.
Keyboard Shortcut: Alt+.: If you prefer not to use history expansion, Bash provides a handy keyboard shortcut that performs a similar task.
Press Alt+Period (Alt+.) (or Esc, then .) to insert the last argument from the previous command directly at the cursor position.
Pressing the shortcut repeatedly cycles backward through the last arguments of earlier commands, making it a quick way to reuse paths without typing them again.
Sometimes you only need one particular argument from a command you ran earlier.
Bash lets you retrieve individual arguments by their position using word designators.
For example, suppose you copy a file:
Now you want to reuse either the source or destination path without typing it again:
Before executing these commands, Bash expands them to:
How Argument Positions Work: When referring to arguments by position, Bash counts them like this:
To refer to an argument from a specific command, use:
For example, if you previously ran:
Then:
Other Useful Argument Selectors: Bash also supports selecting argument ranges:
These references are especially useful when working with long file paths or commands that take many arguments, letting you reuse only the parts you need instead of typing everything again.
One of the most useful history expansion features is recalling commands by the text they begin with.
This is also one of the most misunderstood features in Bash.
The correct syntax is ! immediately followed by the search text, without a space.
To see how it works, run a few commands that all begin with ls:
Now rerun the most recent command that starts with ls:
Bash expands it to:
ls -lA /usr/bin > /dev/null
Although all four commands begin with ls, Bash executes only the most recent matching command.
It does not display a list of matches or let you choose between them.
A Common Mistake Many users expect the following command to recall the last command beginning with ls -l:
Instead, Bash first expands !ls to the most recent command beginning with ls, then appends the remaining text:
In other words, the extra -l is treated as a new argument, not as part of the history search.
Search Anywhere in the Command:- If you can’t remember how a command started, Bash also lets you search for text anywhere in the command line using !?string?.
Bash searches for the most recent command containing the word Desktop anywhere in the command line.
It expands to:
The closing ? is optional if the search string is at the end of the line.
The !! history expansion is one of Bash’s most popular shortcuts.
It expands to the entire previous command line, making it perfect for rerunning long commands without typing them again.
The most common use is recovering from a permission error.
For example, suppose you forget to run a command with sudo:
If you get a permission error:
Simply run:
Bash expands it to:
This lets you rerun the command with elevated privileges without retyping it.
Since !! expands to the entire previous command, you can also append additional arguments or redirections.
For example, suppose you display your system’s IPv4 address:
If you later decide to save the output to a file, run:
Bash expands it to:
On systems where sudo is not available, you can use su instead.
Be sure to use double quotes, because history expansion does not work inside single quotes.
The double quotes are important because Bash performs history expansion inside double-quoted strings before passing the command to su.
If you use single quotes, !! is treated as literal text and won’t expand.
A common misconception is that every failed command can be fixed by adding sudo.
That’s only true when the failure is caused by insufficient permissions.
For example, if you run ifconfig command and receive a command not found error, the problem is usually that the net-tools package isn’t installed, not that you need administrator privileges.
Install it using your distribution’s package manager:
Ubuntu and Debian
RHEL, Rocky Linux, and Fedora
Use sudo !! when a command fails because of permissions, not when the command itself is missing.
History expansion happens before Bash executes a command.
Once you press Enter, the expanded command runs immediately, which isn’t ideal when you’re recalling potentially destructive commands.
To see what Bash will execute without actually running it, append the :p (print) modifier to the history expansion.
For example:
If the most recent command beginning with rm was:
Bash prints:
Notice that nothing is executed.
The :p modifier simply displays the expanded command, allowing you to verify that Bash found the correct history entry before running it.
This works with any history expansion, including:
Using :p is a good habit whenever you’re recalling commands that delete files, overwrite data, modify system configuration, or restart services.
If you’d rather preview every history expansion automatically, enable Bash’s histverify option:
To make this behavior permanent, add the same command to your ~/.bashrc file:
With histverify enabled, Bash expands the history reference and places the resulting command back on your command line instead of executing it immediately.
You can review, edit, or cancel the command before pressing Enter, making history expansion much safer for everyday use.
If you make a small typing mistake in the previous command, there’s no need to retype the entire line.
Bash’s quick substitution feature lets you replace one piece of text and rerun the corrected command immediately.
The syntax is:
For example, suppose you mistype the service name:
Instead of typing the whole command again, simply run:
Bash expands and executes:
Quick substitution only works on the immediately previous command, making it ideal for fixing small spelling mistakes.
By default, quick substitution replaces only the first occurrence of the matching text.
For example:
Now replace nginx with apache:
Bash executes:
Notice that only the first nginx was replaced, while the second occurrence remained unchanged.
To replace every occurrence of a string, use Bash’s full substitution modifier with !!:
Bash expands it to:
Here:
Notice that only the first nginx was replaced, while the second occurrence remained unchanged.
Like other history expansions, substitutions support the :p modifier.
If you’re modifying long commands or important file paths, it’s a good idea to preview the result first:
Bash prints the expanded command without executing it, giving you a chance to verify the substitution before pressing Enter.
History expansion doesn’t just let you recall previous arguments, it can also transform them using word modifiers.
These modifiers are especially useful when working with long file paths, saving you from reaching for commands like dirname and basename.
Word modifiers are appended after a colon (:) and can be used with any history word designator.
For example, suppose you edit a configuration file:
You can extract different parts of the last argument (!$) using these modifiers:
These modifiers work entirely within Bash’s history expansion, so no external commands are needed.
A Practical Example – One of the most useful combinations is editing a file and then immediately changing to its parent directory:
Before executing the second command, Bash expands it to:
This saves you from copying and editing long paths manually.
You can combine word modifiers with other history expansions as well.
For example, !!:$:t returns the filename from the last argument of the previous command, while !1551:$:h extracts the parent directory from the last argument of history event 1551.
The ! in !(pattern) is not part of Bash’s history expansion.
Instead, it belongs to extended globbing, a pattern-matching feature that lets you exclude files matching a specific pattern.
Because extended globbing is disabled by default in Bash, you must enable it before using these patterns.
First, check its current status:
If the output shows:
enable it with:
Once enabled, the pattern:
matches every filename except those matching pattern.
For example, to delete everything in the current directory except important_file.txt:
To keep every PDF file while removing everything else:
Before using rm, it’s a good idea to verify which files the pattern matches.
Simply replace rm with ls:
This shows exactly which files would be deleted, making it an easy safety check before running the destructive command.
You can also protect multiple files or file types by separating patterns with the pipe (|) operator:
This command removes everything except:
Enable Extended Globbing Permanently – If you use extended globbing regularly, add the following line to your ~/.bashrc file:
This enables the feature automatically whenever you start a new Bash session.
In Bash, the ! operator can be used to reverse the result of a test.
A common example is checking whether a directory does not exist.
For example:
Here’s how it works:
If /home/avi/Tecmint doesn’t exist, you’ll see:
Otherwise, the output will be
A common use in shell scripts is to stop execution when a required directory is not available.
If the directory doesn’t exist, the script exits with status code 1.
Another common approach is to create the directory automatically.
Although the check makes the script easier to read, it isn’t strictly necessary because mkdir -p already creates the directory only if it’s missing and does nothing if it already exists.
In many cases, you can simply write:
This is shorter and achieves the same result.
The ! operator can also be placed before a command to reverse its exit status.
If the command succeeds, ! makes it fail.
If the command fails, ! makes it succeed.
This is commonly used in if statements to handle error conditions more naturally.
For example:
Here’s what happens:
This makes the script easier to read than checking for a failure with an else block.
Another common use is verifying that a required command is installed before the script continues.
In this example:
Using ! in this way keeps error-handling code simple and easy to follow.
In parameter expansion, the ! character has another meaning.
It lets you use the value of one variable as the name of another variable.
This feature is called indirect expansion.
For example:
This does not work as intended because Bash first expands ${!service} to the value of the variable named nginx, which doesn’t exist in this example.
The correct way to use indirect expansion is to store the full variable name in another variable:
The output:
Here the service contains the text nginx_port and ${!service} tells Bash to treat that text as a variable name and return its value.
When used with arrays, ${!array[@]} returns the array’s indices instead of its values.
Output:
This is especially useful when looping over indexed or associative arrays.
You can also list variable names that begin with a specific prefix.
For example:
This prints the names of all shell variables whose names start with BASH_.
A common use of ${!array[@]} is iterating over the keys of an associative array.
Output:
Using ${!array[@]} is the standard and most reliable way to access the keys of an associative array in Bash.
If a command contains an exclamation mark (!) inside double quotes, Bash may treat it as a history expansion instead of ordinary text.
When that happens, you’ll see an error like this:
Example Output:
This happens because Bash interprets !Pass as a history reference and tries to find a previously executed command that starts with Pass.
Since no such command exists, it reports an error.
Solution 1 – The easiest solution is to use single quotes, which disable history expansion.
Single quotes treat everything inside them literally, so the password is passed exactly as written.
Solution 2 – If you frequently work with strings containing !, you can temporarily disable history expansion.
After you’re done, enable it again with:
This issue isn’t limited to passwords.
It can also occur with URLs, filenames, or any other text containing an exclamation mark.
In most cases, using single quotes is the simplest and safest way to avoid the event not found error.
Many uses of ! are part of Bash history expansion, which is designed only for interactive terminal sessions.
When you run a Bash script, history expansion is disabled by default.
You can verify this by checking the histexpand option:
Output:
Because of this, history shortcuts such as the following do not work inside shell scripts:
These features depend on your interactive command history, which isn’t available when a script is running.
The ! operator used for logical negation is completely different and works normally in shell scripts.
For example:
In these examples, ! simply reverses the result of the command or test, and this behavior works in both interactive shells and shell scripts.
In short:
The ! character is much more than the !! shortcut that most Linux users know.
Depending on where it’s used, it can recall commands from your history, reuse arguments, fix typing mistakes, negate test conditions, or even perform indirect variable expansion.
One important thing to remember is that history expansion works only in an interactive Bash session.
If you copy a command containing !! or !$ into a shell script, it won’t behave the same way because history expansion is disabled in non-interactive shells.
In scripts, it’s better to save values in variables instead of relying on history shortcuts.
Once you become familiar with these different uses, the ! character can help you work faster at the command line and write cleaner, more efficient Bash scripts.
How to Use the watch Command in Linux to Monitor Commands in Real Time
9 Deprecated Linux Networking Commands and Alternative Tools You Should Use
Try uutils-coreutils: Rust Alternative to GNU Coreutils
How to Downgrade and Lock Packages With dnf in RHEL Systems
5 Ways to Check Remote Port Connectivity in Linux
How to Resize ext4 Partitions and Filesystems in Linux
If you are using BASH in order to expose the use of !, then you must, at least, clarify that BASH is part of the GNU project not of Linux Kernel, please don’t spread misconception.
Simply Awesome
You might also mention !?
It finds the last command with its’ string argument.
For example, if…
1013 grep tornado /usr/share/dict/words
1014 grep hurricane /usr/share/dict/words
1015 wc -l /usr/share/dict/words
are all in the history then !?torn will grep for tornado again where !torn would search in vain for a command starting with torn.
And `wc !?torn?:2` works to select argument two from the command containing tornado and run `wc` on it.
Dear Edgar,
Your Suggestion is taken into account.
Thanks for your feedback.
I didn’t see a mention of historical context in the article, so I’ll give some here in the comments.
This form of history command substitution originated with the C Shell (csh), created by Bill Joy for the BSD flavor of UNIX back in the late 70’s.
It was later carried into tcsh, and bash (Bourne-Again SHell).
Personally, I’ve always preferred the C-shell history substitution mechanism, and never really took to the fc command (that I first encountered in the Korne shell).
Dear Stephen,
Thanks for the wonderful piece of Information.
Keep connected and keep us aware of such context.
4th command.
You can access it much simpler.
There are actually regular expressions:
^ is at the begging expression
$ is at the end expression
:number any number parameter
Example:
touch a.txt b.txt c.txt
echo !^ –> display first parameter
echo !:1 –> also display first parameter
echo !:2 –> display second parameter
echo !:3 –> display third parameter
echo !$ –> display last (in our case 3th) parameter
echo !* –> display all parameters
And we did the same
echo “1st Argument is : !^”
$ echo “2nd Argument is : !cp:2”
echo followed by 1st argument and 2nd argument is just to make the tutorial and command understandable.
I have used ‘!^’.
‘!command:number’ as you are suggesting.
I would like to know if you mean something different?
I think (5) works differently than you pointed out, and redirection to devnull hides it, but ZSh still prints the command.
When you invoke “! ls…”, it always picks the last ls command you executed, just appends your switches at the end (after /dev/null).
One extra cool thing is the !# operator, which picks arguments from current line.
Particularly good if you need to retype long path names you already typed in current line.
Just say, for example
cp /some/long/path/to/file.abc !#:1
And press tab.
It’s going to replace last argument with entire path and file name.
Tomasz,
For your first part of feedback: It doesn’t pick the last command executed and just to prove this we have used 4 different switches for same command.
($ ! ls $ ! ls -l $ ! ls -la $ ! ls -lA ).
Now you may check it by entering the keywords in any order and in each case it will output the same result.
As far as it is not working in ZSH as expected, i have already mentioned that it i have tested it on BASH and most of these won’t work in other shell.
For the second part, what you mentioned is a HASH TAG in Linux Command Line and we have included it in one of our article.
You may like to read it here: https://www.tecmint.com/linux-commandline-chat-server-and-remove-unwanted-packages/
Nice Article
Welcome Manu, Keep Connected!
Great post.
Thanks, I’ve reshared it to G+
One note, “su” stands for “switch user” not “Suitable User”.
Minor note.
At least that’s still representative of what it actually does, unlike when most people refer to it as “Super User”, thinking it only grants root user status, which is utterly incorrect.
@Tachyon,
thanks corrected in the write up..
Install OpenNMS Network Monitoring Tool in CentOS/RHEL 7
7 Best Tools to Monitor and Debug Disk I/O Performance in Linux
9 Useful Commands to Get CPU Information on Linux
Darkstat – A Web Based Linux Network Traffic Analyzer
Understand Linux Load Averages and Monitor Performance of Linux
Amplify – NGINX Monitoring Made Easy
How to Permanently Disable Swap Partition in Linux
How to Control Kernel Boot-Time Parameters in Linux
Exodus – Safely Copy Linux Binaries From One Linux System to Another
How to Learn dd Command in Linux [15 Useful Examples]
5 Advanced Archive Tools for Linux Command Line – Part 2
How to Start, Stop, and Restart Services in Linux
16 Best Tools to Access Remote Linux Desktop
5 Best Open-Source PDF Annotation Tools for Linux in 2024
7 Best Email Clients for Linux Systems
10 Best Linux File and Disk Encryption Tools (2024)
Top 7 Free Odoo Apps for Linux Users in 2025
7 Best Skype Alternatives for Linux in 2025
—
**📚 Original Source:**
[15 Bash (!) Operator Examples Every Linux User Should Know](https://www.tecmint.com/logical-not-operator-linux-commands/)
