I just got a new MacBook and couldn't remember how to set up the fish shell the way I like ๐คท.
Below you can find my notes on setting it up from scratch on macOS.

Setup
Install fish
Run the following command to install fish using Homebrew:
brew install fish
Set fish as the default shell
Run the following command to add fish to the list of available shells and set it as the default shell:
echo /opt/homebrew/bin/fish | sudo tee -a /etc/shells
chsh -s /opt/homebrew/bin/fishecho /opt/homebrew/bin/fish | sudo tee -a /etc/shells
chsh -s /opt/homebrew/bin/fishMake fish recognize Homebrew's binaries
Add the Homebrew's binaries to the list of fish known binaries (otherwise it won't run commands installed with brew) with the following command:
fish_add_path "/opt/homebrew/bin/"fish_add_path "/opt/homebrew/bin/"Create completition files
Parse manual pages installed on the system and attempt to create completion files in the fish configuration directory with the following command:
fish_update_completions
Customizations
Install fisher and fisher plugins:
- Install the fisherplugin manager
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fishercurl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher- Install zto quickly jump across known directories:
fisher install jethrokuan/zfisher install jethrokuan/z- Install fzf, a CLI fuzzy-finder, and make it work amazingly well with fish:
brew install fzf && fisher install patrickf1/fzf.fishbrew install fzf && fisher install patrickf1/fzf.fish- Install a plugin that avoids issues where fish doesn't recognize global npmscripts:
fisher install rstacruz/fish-npm-globalfisher install rstacruz/fish-npm-global- Install a fish-compatible version of nvm:
fisher install jorgebucaran/nvm.fishfisher install jorgebucaran/nvm.fishChange the fish shell prompt (color/theme)
Add the following to ~/.config/fish/functions/fish_prompt.fish to change the fish theme:
function fish_prompt
    set -l pointer_color red
    test $status = 0; and set pointer_color yellow
 
    set -q __fish_git_prompt_showupstream
    or set -g __fish_git_prompt_showupstream auto
 
    if not set -q VIRTUAL_ENV_DISABLE_PROMPT
        set -g VIRTUAL_ENV_DISABLE_PROMPT true
    end
 
    set_color yellow
    printf '%s' $USER
    set_color normal
    printf ' in '
 
    set_color $fish_color_cwd
    printf '%s' (prompt_pwd --dir-length=0)
    set_color normal
 
    # git
    set_color white -d
    set -l prompt_git (fish_git_prompt '%s')
    test -n "$prompt_git"
    and printf ' %s' $prompt_git
    set_color normal
 
    # Line 2
    echo
    set_color $pointer_color
    printf 'ยป '
    set_color normal
endfunction fish_prompt
    set -l pointer_color red
    test $status = 0; and set pointer_color yellow
 
    set -q __fish_git_prompt_showupstream
    or set -g __fish_git_prompt_showupstream auto
 
    if not set -q VIRTUAL_ENV_DISABLE_PROMPT
        set -g VIRTUAL_ENV_DISABLE_PROMPT true
    end
 
    set_color yellow
    printf '%s' $USER
    set_color normal
    printf ' in '
 
    set_color $fish_color_cwd
    printf '%s' (prompt_pwd --dir-length=0)
    set_color normal
 
    # git
    set_color white -d
    set -l prompt_git (fish_git_prompt '%s')
    test -n "$prompt_git"
    and printf ' %s' $prompt_git
    set_color normal
 
    # Line 2
    echo
    set_color $pointer_color
    printf 'ยป '
    set_color normal
endAllow restarting the shell by running fish
Back in the day, with zsh I was used to restart the shell by running zsh. To do the same with fish, create a new funcion ~/.config/fish/functions/fish.fish:
# A way to reload the shell ร  la "zsh"
function fish
  source ~/.config/fish/config.fish
end# A way to reload the shell ร  la "zsh"
function fish
  source ~/.config/fish/config.fish
endPrint a new line in the CLI after any command
To print a new line after any command create a new function ~/.config/fish/functions/postexec_newline.fish:
# Print a new line after any command
# https://stackoverflow.com/a/70644608
function postexec_newline --on-event fish_postexec
   echo
end# Print a new line after any command
# https://stackoverflow.com/a/70644608
function postexec_newline --on-event fish_postexec
   echo
endTweak the config file
Here's my ~/.config/fish/config.fish file.
Protip: when running/customizing external programs in the config file it is a good practice to check if the program executable is available (with if type -q COMMAND_NAME) to avoid errors.
if status is-interactive
    # Commands to run in interactive sessions can go here
end
 
# Disable the fish greeting message
set fish_greeting ""
 
# Print a new line after any command
source ~/.config/fish/functions/postexec_newline.fish
 
# Setup brew
eval "$(/opt/homebrew/bin/brew shellenv)"
 
# Clear line on CTRL + C
# Sometimes it still doesn't work well enough on node.js scripts :(
bind --preset \cC 'cancel-commandline'
 
# Auto-switch nvm version on cd
# Requires a ~/.node-version file with a valid node version
# https://github.com/jorgebucaran/nvm.fish/pull/186
if type -q nvm
  function __nvm_auto --on-variable PWD
  nvm use --silent 2>/dev/null # Comment out the silent flag for debugging
  end
  __nvm_auto
end
 
# Pyenv setup
# Requires `brew install pyenv`
if type -q pyenv
  status --is-interactive; and source (pyenv init -|psub)
end
 
# `ls` โ `ls -laG` abbreviation
abbr -a -g ls ls -laG
 
# `ls` โ `exa` abbreviation
# Requires `brew install exa`
if type -q exa
  abbr --add -g ls 'exa --long --classify --all --header --git --no-user --tree --level 1'
end
 
# `cat` โ `bat` abbreviation
# Requires `brew install bat`
if type -q bat
  abbr --add -g cat 'bat'
end
 
# `rm` โ `trash` abbreviation (moves files to the trash instead of deleting them)
# Requires `brew install trash`
if type -q trash
  abbr --add -g rm 'trash'
endif status is-interactive
    # Commands to run in interactive sessions can go here
end
 
# Disable the fish greeting message
set fish_greeting ""
 
# Print a new line after any command
source ~/.config/fish/functions/postexec_newline.fish
 
# Setup brew
eval "$(/opt/homebrew/bin/brew shellenv)"
 
# Clear line on CTRL + C
# Sometimes it still doesn't work well enough on node.js scripts :(
bind --preset \cC 'cancel-commandline'
 
# Auto-switch nvm version on cd
# Requires a ~/.node-version file with a valid node version
# https://github.com/jorgebucaran/nvm.fish/pull/186
if type -q nvm
  function __nvm_auto --on-variable PWD
  nvm use --silent 2>/dev/null # Comment out the silent flag for debugging
  end
  __nvm_auto
end
 
# Pyenv setup
# Requires `brew install pyenv`
if type -q pyenv
  status --is-interactive; and source (pyenv init -|psub)
end
 
# `ls` โ `ls -laG` abbreviation
abbr -a -g ls ls -laG
 
# `ls` โ `exa` abbreviation
# Requires `brew install exa`
if type -q exa
  abbr --add -g ls 'exa --long --classify --all --header --git --no-user --tree --level 1'
end
 
# `cat` โ `bat` abbreviation
# Requires `brew install bat`
if type -q bat
  abbr --add -g cat 'bat'
end
 
# `rm` โ `trash` abbreviation (moves files to the trash instead of deleting them)
# Requires `brew install trash`
if type -q trash
  abbr --add -g rm 'trash'
end
