Fish is my shell of choice. I’ll never fully stop using bash because remote servers have it as default, but I use
fish on my personal machines.
I like it because it’s designed for interactive use, configurable, has good defaults, and fish are cool…
Here are some features I use and how I configure them.
Abbrs are like bash aliases but less arcane because they expand after you type them and hit space, showing what they do. Here’s an example:
abbr -a g git
Now whenever I type
g, it expands to
git. This is nice because I can see what the alias is doing before I execute the command, it makes my shell history more legible, and anybody looking on knows what exactly my short commands are doing.
fish used to have more friendly abbr system (pre fish 3.6), in my opinion. They added a bunch of features and made it somewhat less automatic. Here are the abbr features I liked prior to fish 3.6:
Currently I don’t have this “universal” style of abbr working, but I do have them persisting to version control now.
Fish never tracked abbrs in version control; they are usually pretty simple, just a text substitution usualy. But I have so many of them, and some of them are nontrivial, so I create and save them using a function called
Functions in fish use a scripting language similar to ruby:
function mkdir-cd --argument dir
mkdir -p -- $dir
and cd -- $dir
Here we have a few features:
and <command> only runs the following command if the previous one succeeds
Fish supports the bash style
|| as well.
Note that functions can change the environment like the current working directory, unlike bash, for which you have to use
source; otherwise this
cd would have no effect.
Rather than naming my functions short names to make them easier to type, I give them descriptive names like
mkdir-cd (makes a directory and changes to that directory), and use abbreviations to type it quickly. For example
abbr-add -a mc mkdir-cd.
Here’s another function example:
if git diff --cached --quiet
git add .
git commit --no-verify -m "wip $argv"
This one uses
$argv to access any arguments following the command. This allows me to type
wip refactoring css without having to quote the message.
One last convention I’ll mention is how I implement default arguments. These aren’t supported natively in fish, but I start an argument with
_ to indicate that this is the “raw” value that was passed on to a function, then use that value to define the version without the leading underscore. In the following example,
_destination is an optional argument variable, and its default value is set on the first line of the function (using my
function clone-cd --argument url _destination
set destination (default $_destination (repo-from-url $url))
if file-exists $destination
cd $destination && git pull
git clone --depth=1 $url && cd $destination
If it is invoked as
clone-cd <repository> <directory>, it will clone the repository into the specified directory. If no second argument is passed, it will use the repository url to come up with the directory name.
url does not have a leading underscore since it is a required parameter and thus has no defaulting behavior.
Fish has a robust event system. Functions that have
--on-event <event> as part of their definition will run when the event happens. The only thing I do for this for now is to source my
~/.config/fish/config.fish after editing them. Here is the implementation, which I put in my
function postexec-source-profile --on-event fish_postexec
set command_line (echo $argv | string collect)
if string match -qr "^$EDITOR " "$command_line"
set file (echo $command_line | coln 2 | string replace '~' $HOME)
set fish_config_files ~/.profile ~/.config/fish/config.fish
if contains $file $fish_config_files
echo -n "Sourcing "(echo $file | unexpand-home-tilde)"... "
Like other shells, fish has programmable tab completion. Here’s a completion I use for my function
echo-variable, which I put in
complete -x -c echo-variable -a '(set | coln 1)'