Skip to content

Bash Style Command Substitution and Chaining (!! !$)

Fabian Boehm edited this page Jun 8, 2024 · 4 revisions

Since fish 3.6, abbr can be used for a nice replacement for bash's history substitution features.

For just !!:

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

The rest of this article is outdated and should be rewritten.


I tried, I really tried to give up bash's history substitution. For the most part, I'm successful, but the 'sudo !!' and 'mkdir ...; cd !$' habits are really strong.

So, while I still use fish's vastly improved command line editing features, I did set up the following:

> funced fish_user_key_bindings

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

function bind_dollar
  switch (commandline -t)
  # Variation on the original, vanilla "!" case
  # ===========================================
  #
  # If the `!$` is preceded by text, search backward for tokens that
  # contain that text as a substring. E.g., if we'd previously run
  #
  #   git checkout -b a_feature_branch
  #   git checkout main
  #
  # then the `fea!$` in the following would be replaced with
  # `a_feature_branch`
  #
  #   git branch -d fea!$
  #
  # and our command line would look like
  #
  #   git branch -d a_feature_branch
  #
  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

> funcsave bind_bang bind_dollar fish_user_key_bindings

The upshot is that !! gets replaced by the previous command, and that !$ gets replaced by the previous command's last argument.

On the one hand, this is far from complete history substitution, just the two features thereof that account for 95% of its use.

On the other hand, it's superior to Bash history substitution in that the replaced text is added immediately to the current command line, and can be edited.

alternative

bind \cs 'commandline "sudo $history[1]"'

note the single quote is necessary to prevent $ substitution from occurring immediately

!$ is provided already by the alt+. keybind. in fish 2.3 you can press escape+..

Abbr based solution for !! (since 2.2.0)

This runs as a hook after every command and make/replace the abbr !! with "sudo last ran command". Then you can just run this abbr by typing !!<RET> or let it expand by typing !!<SPC>.

function sudobangbang --on-event fish_postexec
    abbr -g !! sudo $argv[1]
end

Note for vi or hybrid mode

If you are running vi or hybrid mode, you'll need to specify insert mode in the bindings within the fish_user_key_bindings file, for example:

function fish_user_key_bindings
  fish_hybrid_key_bindings
  bind -M insert ! bind_bang
  bind -M insert '$' bind_dollar
end

getting $$ and $?

These will substitute the correct fish versions of these concepts without you having to type another command.

# enables $?
function bind_status
  commandline -i (echo '$status')
end


# enables $$
function bind_self
  commandline -i (echo '$fish_pid')
end


# enable keybindings
function fish_user_key_bindings
  bind '$?' bind_status
  bind '$$' bind_self
end