I have been using Zsh for almost a decade. I remember the day that I switched from the default shell Bash to ZSH. Powered by Oh-My-Zsh, my terminal was powerful and helpful. Add on top of that Oh-My-Zsh amazing templates; it was also pretty!
It was an amazing experience, and I recently had that experience again with Starship.rs. Switching to Starship.rs meant I broke up with Oh-My-Zsh, and thus Zsh. With everything back up in the air, I thought this would be a great time to try the Fish shell!
If you want to see why you should make this switch without reading all my specific setup, click here
Since I am on a Macbook Pro and already set up homebrew as my package manager, installing Starship.rs was easy. You need to install Starship.rs through Homebrew. Run the below command and get Fish installed as well!
$ brew install starship fish
With Starship installed, let’s update the terminal a bit. Create a configuration file for Starship:
$ mkdir -p $HOME/.config
$ touch $HOME/.config/starship.toml
Feel free to read this documentation to customize your prompt how you like it. Here is a snippet of mine. It seems to work for me for now, but terminal prompts are so personal:
command_timeout = 10000
# Inserts a blank line between shell prompts
add_newline = true
min_time = 4
show_milliseconds = false
disabled = false
format = " 🕙 $duration($style) "
style = "bold italic #87A752"
After completing the install through homebrew, jump into the fish terminal.
fish and see that you can start using Fish right away.
Note: You can always type
exit to get back to your default shell.
But we want a more permanent solution, below is how we add Fish to our available shells and set it as default:
$ echo /usr/local/bin/fish | sudo tee -a /etc/shells
$ chsh -s /usr/local/bin/fish
These commands mean you are officially using Fish as your shell.
No configuration needed: fish is designed to be ready to use immediately, without requiring extensive configuration.
On the homepage for Fish is the above quote. The shell prides itself on clear defaults and powerful built-in features. But I am a tinkerer and need to configuration Fish a bit. These are the steps I took to customize.
To facilitate the configuration of Fish, we need to create a config file. If you have done any customization of Zsh or Bash you are already familiar with this concept. Where we make these changes is a bit different. Let’s create the file we need:
$ mkdir -p $HOME/.config/fish
$ echo "set -gx EDITOR emacs" >> $HOME/.config/fish/config.fish
The above commands make sure we have a folder to hold our Fish configuration and sets my
EDITOR variable to emacs.
The next major change we want to do to our Fish configuration file is tie in Starship.rs.
$ echo "starship init fish | source" >> $HOME/.config/fish/config.fish
And with that, we should be using our Starship prompt with Fish!
Using Fish comes with a few oddities that we’ll have to deal with. The biggest difficulty when moving to Fish is the change in how aliases work.
With Zsh I set up some aliases to make my life easier:
# My Aliases
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias mkdir='mkdir -p'
alias which='type -a'
alias ..='cd ..'
alias ...='cd ../..'
alias ls='exa -lag --header'
alias ccd='clear && cd'
alias killpyc='find . -name \*.pyc -delete'
alias tmux="tmux -2"
alias latex="docker run -v `pwd`:/tmp latex pdflatex"
The most important alias I want to cary over Fish is my
ls alias which uses exa.
I have fallen for
exa, and seeing the normal
ls is disappointing.
Other aliases are better defaults, but will be helpful to have these in Fish too!
Fish has two different kinds of abbreviations.
abbr is manages abbreviations - user-defined words that are replaced with longer phrases after they are entered. 1
alias is a simple wrapper for the
function builtin, which creates a function wrapping a command.2
Since we are trying to mirror Zsh’s aliases, we’ll use Fish’s
First, let’s create a file to hold our abbreviations. Fish has a standard location for additional config files, so let’s create that file and edit our abbreviations:
$ mkdir -p $HOME/.config/fish/conf.d/
$ touch $HOME/.cofig/fish/conf.d/abbr.fish
And fill it out:
abbr rm "rm -i"
abbr cp "cp -i"
abbr mv "mv -i"
abbr mkdir "mkdir -p"
abbr h "history"
abbr which "type -a"
abbr ls "exa -lag --header"
Once the abbreviations file is complete, we can add it to our Fish config file.
source $HOME/.config/fish/conf.d/abbr.fish to the Fish config file.
After reloading the config, we should be able to use our abbreviations.
To test it out, I ran
ls in the
$HOME/.config/fish/ folder and got this:
Permissions Size User Group Date Modified Name
drwxr-xr-x - joshfinnie staff 3 Apr 15:28 completions
drwxr-xr-x - joshfinnie staff 3 Apr 16:19 conf.d
.rw-r--r-- 181 joshfinnie staff 3 Apr 16:21 config.fish
.rw-r--r-- 1.6k joshfinnie staff 3 Apr 16:19 fish_variables
drwxr-xr-x - joshfinnie staff 3 Apr 15:28 functions
Another thing that is different in Fish than Zsh or Bash is how we set Environment Variables. With Zsh or Bash, we’d export a variable to set it globally. But in Fish, there is a different syntax that we will have to use.
Below is an example of setting up the
FZF_DEFAULT_COMMAND in both Zsh and Fish. It’s a small difference, but a noticeable one.
export FZF_DEFAULT_COMMAND='ag -g "" --hidden --ignore .git'
set -x FZF_DEFAULT_COMMAND 'ag -g "" --hidden --ignore .git'
I do not set many environment variables this way, but if you do this should be an easy update.
Although changing shells can be overwhelming, there are some great benefits to using Fish.
- It is fast. I find interacting with Fish to be snappier than Zsh even when customized at the same level.
- It is much easier to customize. There are plenty of services like Oh-My-Zsh that work for Fish. Just to list a few, check out Fisher and Oh-My-Fish.
- There is an amazing ecosystem of functions. I will speak a bit more about functions below.
- Autocomplete and history search are next level. These are available for Zsh too, but Fish’s autocomplete feels infinitely better.
I listed functions as one of the benefits of Fish. And I have to admit I have not used them to their full potential yet. But they are worthy of note.
Within Fish, you can define functions that can do powerful things. The function can iterate over files, or modify files based on boolean checks. Your creativity is the only limit Fish functions have. Below, I write and use a “Hello, World!” function for you to see:
$ function hello
echo Hello $argv!
$ hello Josh
It is that easy.
You can always see what functions are currently available by typing
functions in the Fish shell.
Another option for you is to write and save function in files.
Any file in your
$fish_function_path will work as long as it ends with
By default this path includes
$HOME/.config/fish/functions which is where I’d recommend storing these files.
Per the documentation functions are best use as a stronger alias or abbreviation.
So, as we aliased
exa above, I’ll also create a function for this command:
exa -lag --header $argv
This function saved at
$HOME/.config/fish/functions/lsa.fish will now allow me to use
Doing so will trigger the above function and give me the same output as my abbreviated
This is nice as it does not expand the abbreviation like
But miles might vary on the importance of that feature.
Note: you can also use the
funcsave command that comes with Fish shell to save any functions you created.
So we could save our
hello function above by typing
This will write the function to your function folder automatically!
$ funcsave hello
funcsave: wrote $HOME/.config/fish/functions/hello.fish
This is a quick explanation of how I adopted Fish and Starship.rs for my terminal. I will be honest, I tried Fish five or so years ago and hated it. Let’s see if my experience is better than last time. If you have used Starship.rs and Fish, let me know! Contact me on Twitter and I’d love to chat. So far it has been a nice change-of-pace.