Benjamin Sago / ogham / cairnrefinery / etc…

Technical notes A replacement for ‘!!’ in fish

Something that trips up a lot of new fish converts is the lack of double-exclamation-mark !! command-line substitution. This is a really useful feature that allows you to amend a previous command by prepending or appending extra parameters around it.

Possibly its most famous use is to easily re-run a command with sudo, after you discover you don’t have the permissions you need to run something the first time. You can simply run sudo !! instead of having to re-type or edit the previous command:

$ apt install whatever
E: permission denied, are you root?

$ sudo !!
Installing whatever...

However, this feature, while useful, is the light dangling in front of the anglerfish-like hodgepodge of features that is Event Designators and Word Designators in bash.

Designators in bash

I think these are terrible features and I’m glad the fish developers decided to throw them out.

Many people know of !!, to substitute the previous command, and a few more people know of !!:$ or !$, to substitute the final parameter of the previous command. But there’s also !-2 to substitute the command two commands back, and even stuff like !?file?:$ to substitute the last command that had file in it anywhere. The two common cases (!! and !$) are useful, but their syntax is not.

I take issue with these features because they provide a really easy way to run something other than the command you intended to run. Sure, it’s certainly possible to mentally perform the substitution yourself before running the command… but if you’re going to do that, you’re not going to save yourself much time or typing anyway. In my experience, the only way to be 100% sure that you’re running the command you think you’re running is to have that command displayed in front of you. The Fish FAQ makes note of this as well:

Why doesn’t history substitution (!$ etc.) work?
Because history substitution is an awkward interface that was invented before interactive line editing was even possible. Fish drops it in favor of perfecting the interactive history recall interface.

Furthermore, the ! character often surprises bash users by suddenly gaining special powers after acting like a normal character most of the time. While process substitution (backticks and parentheses) and redirection (> and <) are commonly used, you can use bash for months without being aware of !‘s unique ability. It even gets substituted within double quotes, so the first solution you’d reach for won’t deal with it.

What to do in fish?

The point remains, though, that it’s useful to have an easy way to “sudo-ify” a command in fish without resorting to event or word designators. And fish provides one: if you type Meta-s at the command-line, fish will automatically insert a sudo parameter at the beginning of the line.

So instead of typing a new command with a parameter that gets replaced before it gets run, do the following:

  1. Hit Up or Control-P to go back to your previous command enough times as necessary.
  2. Hit Meta-s, which will insert a sudo parameter at the front.

If you are running a recent version of fish (v3.2.0, released in March 2021) you don’t even have to hit Up, as it will use the previous command automatically.

Another useful keybinding is Meta-., which will insert the final parameter of the previous command. If you hit it more than once in succession, it will cycle through previous parameters.

I have seen people writing their own sudo wrappers that check if the first argument is !!, or even writing custom sudo!! functions, but I can’t recommend doing that now that the Meta-s keybinding exists.