clubmate.fi

A good[ish] website

Web development blog, loads of UI and JavaScript topics

A Fish shell guide

Filed under: Tooling— Tagged with: fish, shell

This is fish shell, the shell for the future, the shell to match your modern home terminal’s needs and requirements.

I taught myself to use fish a while ago, and now, I’m going to write a tutorial about it, and here are some of its quirks and features.

About fish

Fish is a shell, like bash, but different. Some of the selling points include, but are not limited to:

  • Superb completion.
  • More sane scripting language.
  • Super easy, web-based configuration.
  • Is pretty good right out the box.
Fish shell auto-completes almost everything

Installing fish

$ brew install fish

See where your fish is installed:

$ which fish
/usr/local/bin/fish

Now append that to your shells file to make it available:

$ echo "/usr/local/bin/fish" >> /etc/shells

Now you can just do:

$ fish

And you’re using fish. Go back to bash if you like:

$ exit

Updating fish

Check your fish version:

$ fish --version
fish, version 3.1.0

Check if there’s a new version:

$ brew update
$ brew outdated

brew update will update the homebrew package registry, and brew outdated will list out-of-date homebrew packages.

Then just upgrade fish:

$ brew upgrade fish

Reload your terminal window and confirm the version:

$ fish --version
fish, version 3.1.2

Make fish the default shell

You probably want to make it the default shell, issue the "change shell" command:

$ chsh -s /usr/local/bin/fish

Now restart or refresh your terminal and it should load to Fish.

But, if you still see your old bash and you’re using iTerm2, you might have defined the shell in the iTerm2 settings (like I had), just change the setting to "Login shell":

  1. iTerm.app > Preferences > Profiles > General > Command > And select "Login shell".
  2. And run $ chsh -s /usr/local/bin/fish again.

Now iTerm should pick up fish when it restarts.

Configuring fish

Run:

$ fish_config

That will open a web-based GUI where you can select a color theme and configure the look of the shell. When you’re done, come back to the shell and hit enter to exit the config mode.

An important directory for all fish users is ~/.config/fish. All the config lives down there.

Is there .bashrc or .bash_profile in fish?

The file ~/.config/fish/config.fish is equivalent to to .bash_profile.

From there you can export your environmental variables, for example. fish has the --export flag that you can use:

# config.fish
set --export AUTH_TOKEN blahBlah

The bash way of exporting variables seems to work, too:

export AUTH_TOKEN=blahBlah

Install the fish package manager: fisher

Fisher makes it easy to install utilities. All you need to do is to add it to your functions directory:

$ curl https://git.io/fisher --create-dirs -sLo ~/.config/fish/functions/fisher.fish

Syntax for adding packages with fisher:

                 ┌── GitHub path to the repo
           ┌─────┴────┐
fisher add jethrokuan/z

That would install z from github.com/jethrokuan/z.

Listing installed packages:

$ fisher ls

When you install packages, fisher writes them into ~/.config/fish/fishfile. You can then subject this file to version control so you can keep your fish packages in sync between machines.

Running bash in fish

Fish is not POSIX compatible, so bash doesn’t work out of the box. But you can always do:

$ bash

That opens a bash shell, then $ exit to get back.

But if you pass it the -c option, bash will read from a string and you can stay in the current shell:

$ bash -c someBashCommand

Check man bash for more.

Use Bash utilities with Bass

In fish, you can of course run scripts written in any language you imagine if the shebang is correct (#!/bin/bash). But some scripts modify the shell environment, and need to be sourced. This is what fish can’t do, but bass was designed to do just that. It’s a simple python wrapper that calls scripts in bash and passes in and out the needed environmental variables.

Install it with fisher:

$ fisher add edc/bass

For example, you can make nvm (Node Version Manager) work in fish:

function nvm
  bass source (brew --prefix nvm)/nvm.sh ';' nvm $argv
end

Add that function to ~/.config/fish/functions and call nvm normally.

This bridges us up to the nvm options in fish.

Running nvm with fish

We all love nvm, but nvm doesn’t work right out the box in fish. But it’s not hard to get it running.

Wrap npm

Simply wrap nvm with bass, as shown above.

The pure fish nvm: fish-nvm

fish-nvm is an nvm written in pure fish, no shenanigans. It has worked great for me. Install it with fisher:

$ fisher add jorgebucaran/fish-nvm

If you just want something easy that works, you can stop reading now.

Faster shell load with fast-nvm-fish

fast-nvm-fish is a minimal script that aims to make your shell load-up really fast even when you have multiple node version installed. It doesn’t support aliases tho.

More nvms

Fish functions

Bash has aliases and function. Fish simplifies this by only having functions. They’re stored in ~/.config/fish/functions.

Few simple examples to showcase how the functions work:

# ~/.config/fish/functions/ls.fish
function ls
  command ls --color=auto $argv
end

Get the current week number:

# ~/.config/fish/functions/week.fish
function week --description "Gets the current week number"
  date +%V
end

Really handy function to nuke all merged git branches:

# ~/.config/fish/functions/git_delete_merged.fish
function git_delete_merged --description='Deletes merged branches'
  git branch --merged | grep -v '\*' | xargs -n 1 git branch -d
end

You simply call the function by its name to execute it:

$ git_delete_merged

About that --description flag, it supposed to work so that you can see the description next to the function name when you tab complete. But I just can’t get it working. There used to be a bug rendering it useless, but it’s been fixed in the v3.0 of fish. Nonetheless, it doesn’t work for me.

Screenshot of fish’s tab completion output
The description should be next to `git_delete_merged`, but it’s not showing.

Fish function to reload shell on Mac:

function reload --description 'Reloads shell (i.e. invoke as a login shell)'
  exec $SHELL -l
end

You get the idea. Read briefly about functions from the official fish documentation, or more thoroughly in the commands documentation.

Here’s some more fish resources:

Comments would go here, but the commenting system isn’t ready yet, sorry. Tweet me @hiljaa if you want to make a correction etc.

  • © 2021 Antti Hiljá
  • About
  • Follow me in Twatter → @hiljaa
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • console.log('Smash the patriarchy!')
  • I love u!