I’ve been recently nerd-sniped by a geek friend about fish-shell (and that really says it all about what kind of person I am).

Anyways.. I’ve been a zsh and oh-my-zsh user for a long time now, way before zsh was promoted as the default shell on macOS. It’s been a fun ride, full of autocompletions, fancy themes and quick history browsing. I quite enjoyed it, especially compared to the “duller” bash. But since I’m always up for trying out something new and living my digital life on the edge, I decided to give it a try for a while.. especially considering that at work I’m forced to use tcsh.

This brief post is a recap of my experience after playing around with fish for a few weeks.

Autocompletion

The main “selling point” of fish is that it’s dev friendly and provides an interactive autocompletion experience. It’s hard to describe exactly how it feels while typing, that’s something you have to try to get familiar to.. here’s a quick gif to give you a hint:

One important thing to keep in mind is that fish is not POSIX compliant. This might or might not be a big deal, but it’s worth knowing that many things could just change because the writers/mantainers decided it wasn’t worth following these standards and went their own way.

If you’re interested, I definitely recommend visiting http://fishshell.com/docs/current/tutorial.html .

Variables

Variables are definited with the set command.

$ set myvar 'Something'
$ echo $myvar
Something

Environment vars are set via set -x:

$ set -x MY_ENV_VAR 'Something'
$ env | grep MY_ENV_VAR
MY_ENV_VAR=Something

Every variable in fish is just a list: it can contain 0, 1 or more elements. List indexing starts at 1.

$ set myvar 'Something'
$ echo $myvar[1]
Something

For example, to get the first or last path in your $PATH var, you can do:

$ echo $PATH[1]
/Users/vvzen/miniconda3/bin
$ echo $PATH[-1]
/Users/vvzen/miniconda3/condabin

Since $PATH is a string variable in bash, you would do instead:

$ echo $PATH | tr ":" "\n" | head -n 1
$ echo $PATH | tr ":" "\n" | tail -n 1

Python programmers will be immediately familiar with fish’ negative indexing approach to get the last element. Accessing the first element with index 1 is a weird choice (it gave me matlab nightmares), but it is what it is.

You also have slices. This:

$ echo $PATH[1..4]

will print the first four elements in your $PATH. In bash, you could achieve something similar like this:

$ echo $PATH | tr ":" "\n" | head -n 4

But indexing in the middle

$ echo $PATH[3..4]

starts to get a bit longer:

$ echo $PATH | tr ":" "\n" | tail -n 4 | tail -n 2

even though one might argue that isn’t something that you need to do very often.

Substitution

Backticks are bad, long live parenthesis (no need for the dollar sign).

$ echo I\'m (whoami) and it\'s (date +%H:%M:%S)

prints

I am vvzen and it's 14:16:52

In bash this would be:

$ echo I\'m $(whoami) and it\'s $(date +%H:%M:%S)

or

$ echo I\'m `whoami` and it\'s `date +%H:%M:%S`

Commands are not substituted within strings. You need to concatenate them:

$ echo "I am (whoami)"

prints

I am (whoami)

..but

$ echo "I am" (whoami)

prints

I am vvzen

Command substitutions can also be used to create lists, since each \n in your string will be used to split between elements. For example:

$ set my_list (echo -e "value1\nvalue2")
$ echo $my_list[1]
value1
$ echo $my_list[2]
value2

Writings newlines.. in one line!

This is really neat. You can basically write multiple lines in the same command, like this:

You just write whatever you need as you would normally do, no need to manually indent since fish will adjust the indentation for you as you type the right keywords:

To give a silly example, to setup the required env and launch Houdini (on a mac), you could write the following from an interactive session:

$ function launch_latest_houdini
    pushd /Applications/Houdini/Current/Frameworks/Houdini.framework/Versions/Current/Resources/
    set -x HFS $PWD
    set -x H $HFS
    set -x HB "$H/bin"
    set -x HDSO "$H/../Libraries"
    set -x HH "$H/houdini"
    set -x HHC "HH/config"
    set -x HHP "$HH/python2.7libs"
    set -x HT "$H/toolkit"
    set -x HSB "$HH/sbin"
    set -x TEMP /tmp
    set -x JAVA_HOME /Library/Java/Home
    set -x PATH $HB $HSB $PATH
    set -x HOUDINI_MAJOR_RELEASE 18
    set -x HOUDINI_MINOR_RELEASE 5
    set -x HOUDINI_BUILD_VERSON 351
    set -x HOUDINI_VERSION "$HOUDINI_MAJOR_RELEASE.$HOUDINI_MINOR_RELEASE.$HOUDINI_BUILD_VERSION"
    set -x HOUDINI_BUILD_KERNEL "19.3.0"
    set -x HOUDINI_BUILD_PLATFORM (sw_vers -productName) (sw_vers -productVersion)
    set -x HOUDINI_BUILD_COMPILER "10.0.1.10010046"
    popd
    echo "The Houdini $HOUDINI_VERSION environment has been initialized."
    echo "Launching houdini.. 🍥"
    houdini -indie -foreground
end

and then type

$ funcsave launch_latest_houdini

which saves the function in ~/.config/fish/functions/launch_latest_houdini.fish, so that you can reuse it in the future. The best thing is that you can literally just copy-paste the above^ snippet from this blog post it into your fish shell and just run it! No need to open an editor, write a file, etc..

Wrapping it up

PROS

  • Brings the fun back to the shell scripting!
  • Slick ux that can considerably speed up your daily tasks
  • Very good documentation
  • Great as your own personal shell

CONS

  • Adds another thing that could go wrong in the mix
  • Not production proof. Not advised for running build tasks, etc..
  • You need to re-learn a whole new syntax
  • Smaller user base compared to bash, zsh, etc.. That means that you’ll probably have to solve your issues by yourself!

Postscript (17/05/2021)

I tried running houdini from fish and I noticed a lot of crashes tied to memory errors/segmentation faults, even on very a simple scene. Probably it’s unrelated, and it’s just Houdini 18.5 being buggy, but as soon as I run it from the classic launch or from bash these errors seemed to decrease. So I think that the advice regarding “don’t use it in production” holds true.