1. Overview

If we work a lot with the interactive Linux command line, chances are we might have come across the exclamation symbol. On most shells, this symbol is used to rerun previously executed commands through history expansion, thereby, increasing our productivity.

In this tutorial, we’ll discuss several use cases of the “!” symbol to rerun commands from our shell history.

2. Shell History Expansion

Most popular shells like bash, csh, and zsh can store the history of the commands that we have previously executed in the shell. These commands are stored line-by-line in a history file — usually located in our home directory.

When we type “!”, the shell tries to expand that command by replacing it with a previous command that corresponds to the argument. If there’s no match, the shell will complain and return code 1. This can be quite handy when we deal with typing long commands.

This feature is, by default, already enabled on popular shells. However, if you don’t like it you can disable it with set +H.

Let’s see the magic of the “!” symbol in action.

2.1. Repeating the Last Command (!!)

Usually, as an alternative to the and combo, we can type in “!!” to repeat the most recent command:

$ echo "Hello, World"
Hello, World
$ !!
echo "Hello, World"
Hello, World

As an example, if we want to kill multiple instances of a program, we can type in the pkill command once and then repeat it with “!!”:

$ pkill node
$ !!
pkill node

2.2. Repeating a Command by Number (!n)

As we know, the shell keeps track of its history line-by-line in a plain text file. We can rerun a certain command by specifying its line number in the history file as the argument to “!”.

Let’s print the oldest (first) five commands from our bash history file:

$ cat ~/.bash_history -b | head -n5
     1    nvpk
     2    sudo pacman -Ss pipewire
     3    sudo pacman -Ss xdg-desktop
     4    install wofi wofi-calc wofi-emoji-git
     5    shutdown -h 60

The -b option of cat will prepend the line numbers to the lines. We can use this information to repeat the fifth command in our history file:

$ !5
shutdown -h 60
Shutdown scheduled for Sun 2022-05-10 02:07:31 PKT, use 'shutdown -c' to cancel.

The line number of each command won’t change as long as we don’t clear our shell history. Therefore, we can repeat a long command by remembering its line number in the file (takes time but saves time).

Similarly, we can also provide a negative number to “!” to rerun the nth previous command in the history file:

$ cat ~/.bash_history -b | tail -n5
    96    nvim ./set-bg
    97    nvim ../../.config/sway/config
    98    ./set-bg
    99    grep Verbose /etc/pacman.conf
   100    sudo pacman -Ss wofi
$ !-2
grep Verbose /etc/pacman.conf
#VerbosePkgLists

2.3. Repeating a Command Using a Pattern (!pattern)

The hassle of searching for line numbers of previous commands in the history file can be time-consuming. Fortunately, we can type in the “!” symbol followed by a pattern to execute the previous command that matches that pattern.

For instance, let’s execute our last command that involved grepping something:

$ !grep
grep Verbose /etc/pacman.conf
#VerbosePkgLists

However, we should know that it expects the starting pattern only. The following won’t work:

$ !pacman.conf
bash: !pacman.conf: event not found

2.4. Reusing the File Path in the Previous Command (!$)

Sometimes, we might not need to repeat the whole command and we only need part of a previous command. Luckily, “!” has us covered in that regard as well.

For instance, if we want to reuse the file path provided in the previous command, we can use “!$”:

$ cat ~/.bash_history
     1  nvpk
     2  sudo pacman -Ss pipewire
     3  sudo pacman -Ss xdg-desktop
     4  install wofi wofi-calc wofi-emoji-git
     5  shutdown -h 60
     .
     .
     .
$ file $?
file ~/.bash_history
/home/hey/.bash_history: ASCII text

2.5. Reusing the Arguments of a Previous Command (!*)

We can also reuse the arguments of the previous command. Consider an example that creates a bunch of text files:

$ touch index.html index.css index.js

Now, if we want to remove the index.js file, we can simply refer to that argument with “!:3”:

$ rm !:3

Similarly, we can specify a range of arguments with “!:n-m” notation:

$ touch style.js head.css foot.css side.css
$ mv !:1-3 style/

The command will move the head.css, foot.css, and side.css files to the style/ directory.

Moreover, we can also use all the arguments of a previous command with “!*”.

2.6. sudo Commands (sudo !!)

We can also execute the previous command that requires root privileges by typing sudo before the “!” symbol:

$ mount /dev/sda3 /mnt
mount: /mnt: must be superuser to use mount.
$ sudo !!

3. Alternative: Searching Through Shell History

Back in the old days, there were no shortcuts implemented in interactive shells. So, the “!” symbol was the go-to tool for Linux users to repeat the previous command.

Over time, the shells got very advanced and introduced many productive ways to use the interactive command line. One of the features includes searching through the shell history with +R:

$ grep Verbose /etc/pacman.conf
bck-i-search: gre|

When we type +R, the shell prompts us for a pattern. As we type, the shell will display a command that closely matches our typed pattern. Therefore, it’s reactive and swift to figure out what we want to execute.

If there are multiple results for a given pattern, we can press +R again to cycle through the matches.

So, in short, we’re better off using this approach instead of using the history expansion feature for repeating the last commands. However, we can always use history expansion if we need parts of a previous command.

4. Conclusion

In this tutorial, we discussed the history expansion feature of the interactive shells and went through its several use cases. Finally, we covered an alternative to using the history expansion feature that involves quickly searching through the shell history.