fish (Shell) for a Week

I’ve been meaning to try out fish for ages, and I was finally inspired to dive in after reading Julia Evans’ recent take on it. In this post, I’ll describe some highlights from my first week.

Installation and Setup

fish is easy to install: It’s available in both Homebrew and APT. After you’ve tested it and are ready to make it your system default, you can select it with chsh -s, but be warned that on OS X, you’ll first need to add an entry to /etc/shells.

One of the first things you’ll probably want to do when switching shells is to put a few things on your executable path. To do this, open up ~/.config/fish/ and add a line like set PATH /usr/local/Cellar/mtr/0.87/sbin $PATH .

It’s also likely that you use a few tools that integrate with your shell and required manual setup when you first installed them. For me, the first two that I missed were autojump and a rudely-named command corrector. For tools installed via Homebrew (as these are), you can usually get a refresher on the installation instructions with e.g. brew info autojump.


Version managers are great, but they can easily destroy your shell startup time. I’ve been frustrated with that situation in the past, so with this in mind, I decided to try fast-nvm-fish. It’s less feature-rich than a typical nvm installation (notably, aliases don’t work properly), but I’m able to easily switch between installed node versions with e.g. nvm use 6.10.3, which is all I really need.

I haven’t needed any other version managers (rbenv, pyenv, etc.) yet, so I can’t speak to their compatibility. fish is notably not POSIX-compliant, so be prepared for a little extra work to configure lesser-used tools.


fish has a bunch of cool features, like a wide color palette and a convenient web-based configuration interface, but these are my favorites:

Searchable History

fish’s history search is much more intuitive than Bash’s. Type a substring of the thing you want, and browse matches with up/down (or ctrl+p / ctrl+n):

Really Smart Tab Completion

My other favorite feature is fish’s tab completion, which uses information from a variety of sources.

The file seeking completion makes my life easier. It seems like a minor improvement over Bash, but fish’s ability to match substrings within filenames flattens out a frequent speedbump. For example, the other day I entered /Applications/Az (TAB), and fish expanded it into /Applications/Microsoft\ Azure\ Storage\ This is exactly what I wanted, though I didn’t know that the directory started with an M.

fish also has handy completion of command line arguments. Here I search the description of a git command line argument, and then search for a remote branch:

Note also that fish shows you completions before you even hit tab; press the right arrow (or ctrl+f) to accept the current suggestion.


Though I occasionally write commands with unreasonable numbers of pipes (e.g. git status |grep \.js |awk '{print $2}' |xargs git checkout -f), I’m not a very heavy user of shell scripting. That said, there are some big differences between how fish and Bash do common scripty things. They are documented well, but here are the first few I encountered:

# Short circuit:
foo && bar      # bash
foo; and bar    # fish

# Redirect stderr: 
./foo 2>outfile # bash
./foo ^outfile  # fish

# Command substitution: 
file `which ls` # bash
file (which ls) # fish

I’m Hooked!

I’ve used Bash for years, for the same reason that I suspect everyone else does: It was the default on the Unix systems on which I learned. fish is a great candidate shell to overcome Bash’s inertia in your life because 1) it’s easy to install, and 2) it yields benefits before you climb very high on its learning curve.

I expect to stick with this shell for the foreseeable future. What are you waiting for? Give it a try!