Exclamations marks are normally used to give text some emphasis, but under Bash they are actually a shell keyword:
reader@ubuntu:~$ type -a !
! is a shell keyword
While the term "shell keyword" does not really give us a great indication of what it does, there are multiple things we can accomplish with the exclamation mark. One we have already seen: if we want to negate a test, we can supply the exclamation mark within the check. If you'd like to verify this on your Terminal, try the following with either true or false:
reader@ubuntu:~$ true
reader@ubuntu:~$ echo $?
0
reader@ubuntu:~$ ! true
reader@ubuntu:~$ echo $?
1
As you can see, the exclamation mark reverses the exit status: true becomes false, and false becomes true. Another cool feature of the exclamation mark is that a double exclamation mark will be substituted on the command line with the full previous command, like so:
reader@ubuntu:~$ echo "Hello world!"
Hello world!
reader@ubuntu:~$ !!
echo "Hello world!"
Hello world!
To ensure you're clear about what you're repeating, the command is printed to stdout alongside the output of the command. What's more, we can also choose which part of the command to repeat by using numbers and a colon in combination with an exclamation mark. As always, 0 is reserved for the first argument, 1 for the second, and so on. A good example of this is as follows:
reader@ubuntu:/tmp$ touch file
reader@ubuntu:/tmp$ cp file new_file # cp=0, file=1, new_file=2
reader@ubuntu:/tmp$ ls -l !:1 # Substituted as file.
ls -l file
-rw-r--r-- 1 reader reader 0 Dec 22 19:11 file
reader@ubuntu:/tmp$ echo !:1
echo -l
-l
The preceding example shows that we used !:1 to substitute the second word of the previous command. Note that if we repeat this for the ls -l file command, the second word is actually the -l flag of the ls command, so don't assume only full commands are parsed; this is a simple whitespace delimited index.
There is one killer feature with exclamation marks, as far as we're concerned: the !$ construct. It is the same type of substitution, and as you might guess from how $ works in vim, it substitutes the last word of the previous command. While this might not seem like that big a deal, take a look at how often the last word of the previous command is something you can reuse:
reader@ubuntu:/tmp$ mkdir newdir
reader@ubuntu:/tmp$ cd !$
cd newdir
reader@ubuntu:/tmp/newdir
Or, when copying a file you want to edit:
reader@ubuntu:/tmp$ cp file new_file
reader@ubuntu:/tmp$ vim !$
vim new_file
Once you start using it in practice, you'll find that this trick applies to so many commands that it will start saving you time almost immediately. In these examples, the names were short, but if we're talking long pathnames, we'd either have to take our hands away from the keyboard to do a copy/paste with the help of our mouse, or type everything again. And why would you, when a simple !$ does the trick?
In the same way this can quickly become a lifesaver, there is one extremely good example of when to use !!. Take a look at the following situation, which everyone has encountered or will encounter sooner or later:
reader@ubuntu:~$ cat /etc/shadow
cat: /etc/shadow: Permission denied
reader@ubuntu:~$ sudo !!
sudo cat /etc/shadow
[sudo] password for reader:
root:*:17647:0:99999:7:::
daemon:*:17647:0:99999:7:::
bin:*:17647:0:99999:7:::
<SNIPPED>
When you forget to add a sudo in front of your command (because it is a privileged command or manipulates a privileged file), you can either:
- Type the whole command again
- Copy and paste the command using the mouse
- Use the up arrow, followed by the Home key, and type sudo
- Or simply type sudo !!
It should be clear which is the shortest and easiest, and thus has our preference. Do realize that with this simplicity also comes responsibility: if you try to remove files you should not remove, and you're quickly using sudo !! without fully thinking it through, your system could be gone in an instant. The warning still stands: when interacting as root or with sudo, always think twice before running a command.