How do I rerun the second-to-last command?

I'm using Ubuntu 21.10. If I mess up, I can re-run the last command with !!. Here's an example:

$ apt update
Reading package lists... Done
E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)
E: Unable to lock directory /var/lib/apt/lists/
W: Problem unlinking the file /var/cache/apt/pkgcache.bin - RemoveCaches (13: Permission denied)
W: Problem unlinking the file /var/cache/apt/srcpkgcache.bin - RemoveCaches (13: Permission denied)
$ sudo !!
sudo apt update
[sudo] password for [me]:
...
Fetched 336 kB in 2s (206 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
98 packages can be upgraded. Run 'apt list --upgradable' to see them.

That's fine, but instead of rerunning the previous command, how can I run the one before it? Like this:

$ echo hi
hi
$ echo hello
hello
$ !!   <-- I'm trying to get that to run 'echo hi'
hello

Basically, here's what I'm looking for

$ echo hi
hi
$ echo hello
hello
$ ???   <-- What can I put here to get it to run 'echo hi'?
echo hello!
hello!

So, given that !! runs the last command, how can I run the command before the previous one? In my example, instead of typing !!, what can I run to execute echo hi?


Solution 1:

You can use the up-arrow and down-arrow keys to cycle through your command history. So to re-run the second-last command, you'd press up-arrow twice, then return.

This has several advantages over methods like !-2. Most importantly (in my opinion), you can see the command and make sure it's the one you intended to re-run before pressing return to execute it. Even aside from simple mistakes ("oops, I guess it was the third command back that I wanted"), bash history may not count previous commands the way you expect because of options like ignorespace, ignoredups, and the HISTCONTROL variable.

Also, interactive command recall lets you easily edit the command before re-running it. You can do that with history recall modifiers as well, but again doing it interactively lets you see your edits before executing the recalled/modified command.

Plus if you're lazy like me, up-arrow up-arrow return is easier to type than (shift)exclamation minus 2 return

Solution 2:

From the HISTORY EXPANSION section of man bash:

!-n    Refer to the current command minus n

So to run the command before last, you can use !-2

In fact, !! is just a synonym of !-1

Solution 3:

!-n execute command "-n" of your history, but I wouldn't recommend it, as it is "instant" and will not give you time to check first that the "Nth" command is the right one you need to execute again... ( @bac0n nicely remarked that you can set : shopt -s histverify to switch it to "non instant": it will let you verify the command !-n expands to, before executing it with <Enter>)

However, if you are using bash as your shell, I much prefer to recommend using the search feature:

Ctrl and then type something, which is the part of the command that you are looking for. (ex: Ctrl + r + (type into terminal) echo)

It will present the latest occurrence in your history matching something (i.e., matching the search term that you typed after Ctrl + r).

Subsequent Ctrl + r will look further back in your history. Or adding letters will precise the search.

And once you find a good match, press Enter to execute it again (or Ctrl + c to exit from the search without executing the currently displayed matching command).

Additional tip: if, instead of Enter , you press Ctrl + o , it will execute the selected history command AND present the next one, ready to be executed with Enter (which only execute that one and returns to the normal prompt) or Ctrl + o again (which executes it and present the next command in your history). Very useful to repeat a sequence of steps, and providing a display of those commands before executing them.

And each time you use those things, you are allowed to change something on the currently displayed line (using the left or right arrow key to move within it), such as a parameter, a server name, etc., and execute that modified line + go to the next one (if you pressed Ctrl + o).