Razzi's guide to fish shell

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 abbr-add.


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:

Fish supports the bash style && and || 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:

function wip
    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.

Default arguments convention

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 default function):

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.

Note that 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 ~/.profile and ~/.config/fish/config.fish after editing them. Here is the implementation, which I put in my ~/.config/fish/config.fish.

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)"... "
            source $file
            echo done.


Like other shells, fish has programmable tab completion. Here’s a completion I use for my function echo-variable, which I put in ~/.config/fish/completions/echo-variable.fish:

complete -x -c echo-variable -a '(set | coln 1)'

See Also

Depends On