Aral Balkan

Mastodon icon RSS feed icon

Fish shell

An ASCII drawing of a fish

Fishing for a new shell? (Sorry.)

Beautiful defaults

Good design isn’t about removing the seams altogether1; it’s about having beautiful defaults and layering the seams. And this is something that Fish shell absolutely nails.

To be fair, I have no idea why I didn’t give it a proper shot until yesterday. I guess because Zsh, or more precisely Oh My Zsh, has been perfectly adequate.

It’s not until you experience the intelligent auto-suggestions and completions in Fish, however, that you truly realise what you’ve been missing. And that’s before you learn just how easy it is to extend as nearly everything is a function.

Getting started

First off, download and install Fish shell.

I’m running elementary OS on my Star Labs notebook so I followed the Ubuntu instructions:

sudo apt-add-repository ppa:fish-shell/release-3
sudo apt update
sudo apt install fish

At this point, you can simply type fish in your current shell to open a Fish shell instance.

Screenshot of auto-suggest for 'git remote' + tab, with the add subcommand suggestion selected. From the history, the prompt is suggesting completion to 'origin' and, underneath, it shows a list of other subcommand suggestions along with their descriptions: add (Adds a new remote), set-branches (Changes the list of branches tracked by a remote), get-url (Retrieves URLs for a remote), set-head (Sets the default branch for a remote), prune (Deletes all stale tracking branches), set-url (Changes URLs for a remote), remove (Removes a remote), show (Shows a remote) …and 2 more rows

Out of the box, you get the excellent auto-suggest/completion.

I recommend you take a moment to at least skim through the comprehensive tutorial, documentation, and frequently-asked questions.

Sprucing it up

While Fish shell is perfectly usable without customisations, there are a few things I’ve gotten used to having in my shells that I wouldn’t want to do without. These range from the practical to the merely cosmetic.

The first thing I’d highly recommend you do is install Fisher.


Fisher is a plugin manager for Fish. It does one thing and does it well.

You can install it by running the following one-liner (this is the script that it will run on your machine):

curl -sL | source && fisher install jorgebucaran/fisher

From there, you can browse the list of Fisher plugins on

Following is a brief list of the ones I installed.


Tide is a modern prompt manager for Fish.

After playing with it a little, I forked it to customise the Git prompt (I like to have my Git prompt tell me what the status is in words instead of cryptic symbols.)

Screenshot of my prompt. It reads: ~/ ❨git❩  master 1 untracked

A word is worth a thousand symbols. Or something.

Also, note that in the screenshot you can see a count of the files that fall into different git statuses. Normally, this would slow a prompt down but Tide is about as snappy as can be because it is asynchronous so you are never blocked from entering a command while waiting for your prompt to render.

Install it using Fisher:

fisher install IlanCosman/tide


Screenshot of Gills showing the empty line after the command and the command output

Fish shell gives you gills (I need help, I know.)

Creating a plugin is so simple in Fish that I’ve already made a very simple one even though I only just started using the shell yesterday.

The plugin, which I call Gills, is about as simple as it gets: it just adds an empty line after your prompt and before the output of your command to balance the whitespace around them so you can more easily separate your prompts from your command output while skimming your terminal’s scrollback buffer.

It’s called Gills because it gives your output room to breathe. Get it? (Did I mention I was sorry?)

Technically, like basically everything else in Fish shell, it’s just a function. In this case, one that handles Fish shell’s fish_postexec event.

(It also handles a couple of special cases like cd, pushd, and popd that do not produce any output. In those cases, it doesn’t add the additional empty line. If you find any other edge cases, please feel free to open an issue or a pull request.)

Install it using Fisher:

fisher install small-tech/gills

Node Vesion Manager (nvm)

I work with Node.js everyday so, prior to Fish shell, I was using nvm-sh/nvm to manage my node versions.

While you can still use that script in Fish, there is a better option that’s called

Designed for Fish, this tool helps you manage multiple active versions of Node on a single local environment. Quickly install and switch between runtimes without cluttering your home directory or breaking system-wide scripts.

Install it using Fisher:

fisher install jorgebucaran/

From there on, you can use commands like nvm install lts to quickly install and use different versions of Node. also supports .node-version and .nvmrc files so pop one of those bad boys into your project directory to automatically have the Node version specified within them available when you switch to your project’s directory in your shell.

Puffer Fish

One feature of Zsh that I use all the time is to navigate back multiple directories simply by adding more dots to my command. So, for example, if I’m in the /a/b/c/d/e/ directory and I want to change to the b folder, I can simply write cd .... and Zsh will interpret that as cd ../../../ and save me some typing.

(Hey, I’m a developer, being lazy is part of the job description.)

So, anyway Puffer Fish enables Fish shell do the same thing. Actually, it’s even better because you get in-place expansion.

Install it using Fisher:


While Puffer Fish makes it easy traverse backwards through a directory stack, z makes it easy to jump to any (previously visited) directory at any time.

z tracks the directories you visit. With a combination of frequency and recency, it enables you to jump to the directory in mind.

So, for example, if I visit ~/small-tech/small-web/domain and then, later, I want to get back there, I can simply type the following from anywhere:

z domain

And it’ll pop me back into my project folder.

I wasn’t sure if I’d end up using it when I first installed it but I find myself using it quite often to quickly switch to the directories of projects I work on often.

Install it using Fisher:

fisher install jethrokuan/z


Autopair automatically matches opening/closing pairs of common punctuation like quotation marks and parentheses.

It might seem like a small thing, but it’s one of those things that gives you a little jolt of dopamine every time it Just Works™.

Install it using Fisher:

fisher install jorgebucaran/

Dracula theme

I like and use the Dracula theme pretty much everywhere and, of course, there’s a version for Fish.

Install it using Fisher:

fisher install dracula/fish


Screenshot of the web interface for fish_config, showing the  colors, prompt, functions, variables, history, bindings, and abbreviations tabs.

How do you like your Fish?

If you’re coming from Bash or, like me, from Zsh, you might be wondering where to put your aliases, etc. The equivalent of ~/.bashrc or ~/.zshrc in Fish is ~/.config/fish/

You can also configure your shell visually in your web browser by running the following command in your terminal:


Also, while Fish does have aliases, it also has a much more powerful alternative called abbreviations.


Screenshot of Fish shell suggesting abbreviation completions for the command get-lo: git-log (Abbreviation: git log --graph --decorate --pretty=oneline --abbrev-commit) and git-log-dates  (Abbreviation: git log --graph --decorate --pretty=format:"%h [%cr] %s)

Abbreviations? DMIID (Abbreviation: don’t mind if I do)

Abbreviations are just regular Fish functions but you get a handy shortcut (abbr) for defining them. Unlike aliases, you get auto-suggestions for abbreviations and they are expanded in place so you can see the full command.

While I’ve converted most of my Zsh aliases to abbreviations, I’ve kept some (like code for codium) as aliases.

My advice is to convert all your aliases to abbreviations and then you will know right away whether you need to convert any back to aliases when you use them. (e.g., I didn’t want to keep seeing /usr/local/codium on my history so I decided to keep code as an alias.)

Here is my current file, in case it gives you some ideas. I’ve also listed links to the custom commands used to make it easier for you to find and install them.

if status is-interactive
    # Aliases

    # VSCodium
    alias code '/usr/bin/codium'

    # Abbreviations

    # Copy and paste to the clipboard by piping to these commands.
    # (Inspired by the default behaviour in macOS.)
    abbr --add --global pbcopy 'xsel --clipboard --input'
    abbr --add --global pbpaste 'xsel --clipboard --output'

    # Open files in their associated apps from Terminal.
    abbr --add --global open 'xdg-open &>/dev/null '

    # Git aliases to make git a bit more humane for everyday use.
    abbr --add --global git-log 'git log --graph --decorate --pretty=oneline --abbrev-commit'
    abbr --add --global git-log-dates 'git log --graph --decorate --pretty=format:"%h [%cr] %s'
    abbr --add --global git-tag 'git tag -n'
    abbr --add --global git-undo-last-commit 'git reset HEAD~'

    # Aliases for getting system and app information.
    abbr --add --global system-information 'neofetch'
    abbr --add --global disk-usage 'dust'
    abbr --add --global which-kernel 'apt-cache policy linux-generic'
    abbr --add --global node-v8-version 'node -p process.versions.v8'

    # Make rm a little safer (have it prompt once when deleting
    # more than three files or when deleting recursively).
    abbr --add --global rm 'rm -I'

    # A nicer ls that also shows the git status of files
    abbr --add --global l 'exa -lh --git --all'

    # A nicer ls that also shows the git status of files
    # (but not hidden files)
    abbr --add --global ll 'exa -lh --git'

    # Same nicer ls but in tree view.
    abbr --add --global lt 'exa -lh --git --all --tree'

    # lc = line count
    abbr --add --global lc 'wc -l'

    # Find out what’s running on port X
    abbr --add --global port 'lsof -i'

    # Better find
    abbr --add --global find 'fd'

    # Better ps
    abbr --add --global ps 'procs'

    # Package.json validator
    abbr --add --global validate-package.json 'pjv package.json'

    # Use ripgrep instead of grep
    abbr --add --global grep 'rg'

Tools mentioned in the configuration

Next steps

This introductory post doesn’t even begin to scratch the surface of what you can do with Fish but I hope that it sparks your interest and whets your appetite.

To learn more, view the documentation on the Fish shell website. You can also launch the documentation in a browser locally from Fish shell itself using the help command.

For example, to learn about abbreviations, run the following command:

help abbreviations

So, what are you waiting for?

Go Fish!

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.

  1. If you remove the seams altogether, you end up with the shiny locked boxes that Apple ships. Sure, they’re lovely to look at and use but you do so on the ever-evolving unilateral terms imposed by the corporation that makes them. This has considerable ramifications when it comes to protecting your freedom to own, control, and use your tools as you want to and impacts on your human rights (e.g., privacy), right to tinker, right to repair, etc. ↩︎