50

I've been using bash for a decade or so and have gotten used to being able to type !$ to repeat the last argument, e.g.:

$ mkdir foo
$ cd !$
cd foo

(That last line is printed by the shell to tell you what your command evaluated to). Likewise I often to the following:

$ make_sandwich
-bash: make_sandwich: Permission denied
$ sudo !!
sudo make_sandwich

I'm really liking the fish shell, but my muscle memory is pretty established. Is there an equivalent in fish? Can I configure fish to use the same commands?

1
  • For others who find this, I realize that muscle memory is hard to break (it was for me), but the convenience of Alt+S for prepending sudo (see [@jnns's answer])(superuser.com/a/1610597/1210833) is worth the "relearning" effort. Commented Jun 29, 2022 at 1:49

7 Answers 7

45

Alt-Up arrow gives you the last argument from the previous command. Subsequent pushes cycle throught prior arguments.

I haven't found a satisfactory equivalent to !!, except Up then Ctrl-A

3
  • 1
    left Alt-Up arrow didn't do anything in my case. right Alt-Up arrow did do something, but not what I expected, it didn't grab the last argument of the previous command.
    – Flimm
    Commented Jun 21, 2022 at 9:19
  • 2
    alt-. worked for me.
    – Flimm
    Commented Jun 21, 2022 at 9:21
  • 1
    Couldn't seem to get this to work (on macOS). ¯\_(ツ)_/¯
    – clozach
    Commented Jun 28, 2022 at 19:50
25

I found this perfect answer on the fish-users mailing list. Add this to your config.fish file:

function bind_bang
    switch (commandline -t)[-1]
        case "!"
            commandline -t -- $history[1]
            commandline -f repaint
        case "*"
            commandline -i !
    end
end

function bind_dollar
    switch (commandline -t)[-1]
        case "!"
            commandline -f backward-delete-char history-token-search-backward
        case "*"
            commandline -i '$'
    end
end

function fish_user_key_bindings
    bind ! bind_bang
    bind '$' bind_dollar
end

Restart fish. Now, when you type !!, it will automatically be replaced with the last command. And when you type !$, it will automatically be replaced with the last argument of the last command.

Further discussion on fish's github wiki

6
  • 1
    That is awesome! You should consider editing the top answer to include this... Commented Aug 4, 2016 at 15:33
  • 2
    I created ~/.config/fish/config.fish and pasted this. It worked great after restarting fish.
    – Katu
    Commented Nov 20, 2016 at 10:56
  • I feel like this answer and the current top one mentioning alt + up should be combine. This one is great because it's exactly the solution that the question is asking but the current top answer is because it's a more canonical fish way of doing it.
    – klvs
    Commented Apr 28, 2020 at 3:59
  • Couldn't seem to get this to work (on macOS). ¯\_(ツ)_/¯
    – clozach
    Commented Jun 28, 2022 at 19:51
  • I'm on a Mac now, no problems. What's your fish version? Commented Jun 28, 2022 at 20:05
15

For sudo !!

In fish shell (release 3.1b1 and later) you can use the binding __fish_prepend_sudo, which is Alt+S by default, to prepend the currently present command-line with sudo :

$ make sandwich▎ 

Alt+S

$ sudo make sandwich▎

As of fish 3.2.0, Alt+S on an empty line will pull in the most recent line from the history and prepend sudo . Prior to 3.2.0, you'll first need to pull in the most recent line from the history via 🠉.

As of fish 3.5.0, the doas command will be used if it is available on the system but sudo is not.


For prepending commands other than sudo/doas

See the bind_bang function in @GlennJackman's answer


For <command> !$

You can cycle through the arguments of previous commands using the binding history-token-search-backward. The default keybinding is Alt+., as in Bash, but Alt+🠉 and Alt+🠋 can be used as well:

$ mkdir foo
$ rmdir ▎

Alt+.

$ rmdir foo▎

All of fish's bindings can be viewed by executing fish_config and navigating to the "bindings" tab.

1
  • alt-. worked for me, but not alt-up arrow
    – Flimm
    Commented Jun 21, 2022 at 9:21
13

sudo !! (or sudo bang bang) is one of my most oft used commands. I'm still using just plain old bash that has it just fine. Sorry to hear that fish doesn't implement it correctly. A little googling and I found this:

function sudo
    if test "$argv" = !!
        eval command sudo $history[1]
    else
        command sudo $argv
    end
end

There are a lot more options over on the thread here: https://github.com/fish-shell/fish-shell/issues/288

1
  • where do I have to enter that code? entering that into fish and then "sudo !!" returns some error lines with "source: Error while reading file '-'" Commented Sep 23, 2016 at 14:31
6

I had the same problem as you, and I fixed by using oh-my-fish (it's a plugin manager for fish shell) https://github.com/oh-my-fish/oh-my-fish. You can install it with this command :

curl -L https://get.oh-my.fish | fish

Then install the plugin bang-bang with this command :

omf install bang-bang 
2
4

With "global abbreviations" in Fish 3.6.0, it's now possible to reproduce the !! and !$ functionality of Bash (et. al.) more closely than ever. This technique has some advantages and disadvantages when compared to Bash, but if you are working on muscle-memory, I think the new methods compare very favorably. The best thing is that abbreviations are expanded in-place after the next delimiter (usually Space), so you can see (and further edit) the expansion before committing.

For !!

From the Fish doc:

function last_history_item
    echo $history[1]
end
abbr -a !! --position anywhere --function last_history_item

With that in place, !! will be expanded to the previous commandline. E.g.:

which ls
du -h $(!!)

# Automatically expands to
du -h $(which ls)

Of course, sudo !! can be used, but I'd still encourage all Fish users to be aware of the Alt+S shortcut for prepending sudo as mentioned in @jnns's answer -- It's far more efficient and powerful once you break yourself of the !! muscle-memory.

That said, having !! available for other, non-sudo commands is great.

For !$

Probably can be improved on, but here's a first stab at a !$ implementation based on global abbreviations:

function last_history_token
    echo $history[1] | read -t -a tokens
    echo $tokens[-1]
end
abbr -a !\$ --position anywhere --function last_history_token
cat ~/.config/fish/config.fish
less !$

# Automatically expands to:
less ~/.config/fish/config.fish

Note that, at least currently, you do need to add a Space after the !$ in order to force the expansion. This is due to Fish attempting to parse the $ as the beginning of a variable name.

As with the sudo/Alt+S method mentioned above, I really feel that it's just as easy to recall the last token using Alt+.. This method also works in Bash and other shells, so retraining muscle memory to prefer it over !$ is not unique to Fish.

1
1

If you use !! only in the context of sudo !!, you can define a keybinding to ^s (CTRL+s) which prepends sudo to your command:

function prepend_command
  set -l prepend $argv[1]
  if test -z "$prepend"
    echo "prepend_command needs one argument."
    return 1
  end

  set -l cmd (commandline)
  if test -z "$cmd"
    commandline -r $history[1]
  end

  set -l old_cursor (commandline -C)
  commandline -C 0
  commandline -i "$prepend "
  commandline -C (math $old_cursor + (echo $prepend | wc -c))
end

This allows to type in any command and prepend sudo while typing or like in your case as a substitution of sudo !!

See the Ahti's comment on the github discussion

1
  • 3
    For those reading several years later, Fish has now incorporated this (or a similar) function by default with the Alt+s keybinding. See @jnns's answer for more details. Commented Jan 6, 2021 at 22:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .