🐟 Fish Shell Install & Quick Reference

Fish Shell Install Guide and Cheat Sheet / survival guide

Video: fish (Command Line Tips video)
Prefer to watch? This short video runs this page's main commands on a real Ubuntu 24.04 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
fish [options] [file [arguments...]]
sudo apt install fish

fish is the "friendly interactive shell". Compared with bash it works better out of the box:
suggestions as you type (from your history), syntax highlighting that turns red for a bad
command, smart tab completion with descriptions, and a clean scripting language. The catch:
fish is NOT POSIX compatible, so bash scripts and some copy-pasted one-liners will not run in
it as they are. Most people use fish for interactive work and keep writing scripts in bash.

Install

sudo apt install fish                       # Debian/Ubuntu
sudo dnf install fish                       # Fedora/RHEL family
sudo pacman -S fish                         # Arch
sudo zypper install fish                    # openSUSE
brew install fish                           # macOS and Linux (Homebrew)
winget/choco: not native. On Windows, use WSL and install it inside your Linux distribution.
Newer releases on Ubuntu/Debian (optional):
sudo apt-add-repository ppa:fish-shell/release-4    # or release-3; check fishshell.com for the current PPA
sudo apt update && sudo apt install fish
fish --version
Try it first without changing anything:  fish    (type exit to go back to bash)

Make fish your default shell

which fish                                        # for example /usr/bin/fish
echo /usr/bin/fish | sudo tee -a /etc/shells      # allow it as a login shell (use the path from "which")
chsh -s /usr/bin/fish                             # change YOUR shell, then log out and back in
echo $SHELL                                       # confirm
Safer for servers and scripts: keep bash as the login shell and start fish from ~/.bashrc, only when interactive:
# at the end of ~/.bashrc
if [[ $- == *i* && -z $FISH_VERSION && $(ps -o comm= -p $PPID) != "fish" ]]; then
  exec fish
fi
That way scripts, cron and remote commands keep using bash, and you can still recover if fish breaks.
To go back:  chsh -s /bin/bash

First look

fish_config                          # opens a web page to pick a theme and prompt (Ctrl+C to stop the server)
fish_config theme choose "Tomorrow Night"     # newer versions: choose a color theme in the terminal
fish_config prompt choose default             # choose a prompt style
help                                  # open the fish documentation in your browser
fish_update_completions               # build extra completions from man pages
set -U fish_greeting ""               # turn off the welcome message (persists)

The features you get for free

Autosuggestions   Grey text after your cursor from history. Press Right Arrow or Ctrl+F to accept all
                  Alt+Right Arrow (or Alt+F) to accept one word
Highlighting      Valid commands are colored, typos are red, existing files are underlined
Tab completion    Press Tab for options with descriptions; press Tab again to pick from the list
Prefix history    Type the start of a command, then Up Arrow to cycle through matching history
Man page help     Completions for most commands come straight from their man pages
Web config        fish_config edits colors and prompts in your browser

Keyboard shortcuts

Right Arrow / Ctrl+F        Accept the whole suggestion
Alt+Right / Alt+F           Accept the next word of the suggestion (also move forward one word)
Alt+Left / Alt+B            Move back one word
Ctrl+A / Home               Start of line             Ctrl+E / End       End of line
Ctrl+W                      Delete the word before the cursor
Alt+D                       Delete the word after the cursor
Ctrl+U                      Delete to the start of the line         Ctrl+K    Delete to the end
Ctrl+C                      Cancel the current line                 Ctrl+D    Exit (on an empty line) or delete a character
Ctrl+L                      Clear the screen and redraw
Ctrl+R                      Search history (newer versions show a searchable list)
Up / Down                   Cycle history entries that start with what you typed
Alt+Up / Alt+Down           Cycle through previous arguments (words) from history
Alt+E  or  Alt+V            Edit the current command line in $EDITOR (fish 3+)
Alt+S                       Put sudo at the start of the command line (great after "permission denied")
Alt+H                       Show the man page of the command under the cursor
Alt+L                       List the directory at the cursor
Ctrl+X                      Copy the command line to the clipboard (if supported)
Tab / Shift+Tab             Complete / move backward through the completion list
Find the key codes for your terminal: fish_key_reader
Change bindings: bind \cg 'git status; commandline -f repaint'      Make it permanent in config.fish
Use vi keys: fish_vi_key_bindings        Back to default: fish_default_key_bindings

Configuration files

~/.config/fish/config.fish             Runs at startup (your main file)
~/.config/fish/conf.d/*.fish           Extra startup snippets (loaded in alphabetical order)
~/.config/fish/functions/NAME.fish     One file per function (loaded on demand)
~/.config/fish/completions/NAME.fish   Your custom tab completions
~/.config/fish/fish_variables          Universal variables (managed by fish, do not edit by hand)
Typical config.fish. Wrap interactive-only lines in this check so scripts stay fast and quiet:
if status is-interactive
    # commands to run only in an interactive terminal
    set -g fish_greeting ""
    abbr -a gs git status
    abbr -a gc git commit
    alias ll 'ls -lah'
end
fish_add_path ~/.local/bin ~/bin                # add folders to PATH (skips duplicates and missing folders)
set -gx EDITOR vim                              # an exported variable
Reload without restarting:  source ~/.config/fish/config.fish

Variables (this is where fish differs most from bash)

set name value                          # create a variable (no = sign and no $ when assigning)
echo $name                              # use it
set -x NAME value      or  set -gx NAME value       # export it to child programs (bash: export NAME=value)
set -g NAME value                       # global: this session only
set -l NAME value                       # local: inside the current function or block only
set -U NAME value                       # universal: shared by all sessions and saved between restarts
set -e NAME                             # erase (unset) a variable
set NAME a b c                          # a list; $NAME[1] is "a", $NAME[-1] is "c", $NAME[2..3] is a slice
set -a NAME d                           # append to a list      set -p NAME z     prepend
count $NAME                             # number of items
set --show NAME                         # inspect where a variable is set and its scope
set                                     # list all variables
echo $PATH                              # PATH is a list; fish prints one entry per space
set -gx PATH $HOME/bin $PATH            # or simply: fish_add_path $HOME/bin
Environment for one command:  env DEBUG=1 ./run.sh     (VAR=value command also works in fish 3.1+)
Special variables: $status (exit code, bash uses $?), $argv (arguments), $pipestatus, $fish_pid, $last_pid, $HOME, $PWD, $USER, $hostname

Bash to fish cheat table

bash                                    fish
export VAR=value                        set -gx VAR value
VAR=value                               set VAR value
unset VAR                               set -e VAR
echo $?                                 echo $status
$(command)  or  `command`               (command)       (fish 3.4+ also accepts $(command))
alias ll='ls -l'                        alias ll 'ls -l'     or   abbr -a ll 'ls -l'
if [ -f file ]; then ... fi             if test -f file; ...; end
for i in 1 2 3; do ...; done            for i in 1 2 3; ...; end
while cond; do ...; done                while cond; ...; end
case $x in a) ...;; esac                switch $x; case a; ...; end
function f { ...; }                     function f; ...; end
cmd1 && cmd2   cmd1 || cmd2             cmd1; and cmd2   cmd1; or cmd2      (&& and || also work in fish 3+)
!!                                      Press Up Arrow or Alt+Up. (!! is not supported. Use  sudo $history[1] )
source file.sh                          source file.fish   (fish syntax only)
export -f                               use functions saved with funcsave
[[ $a == b ]]                           test "$a" = b       or   string match -q b $a
$((1+2))                                math 1 + 2
${var:-default}                         set -q var; or set var default
read -p "Name: " n                      read -P "Name: " n
cmd 2>&1                                cmd 2>&1       (also  &>  or  &| in fish)
<<< "text"  and heredocs (<<EOF)        echo "text" | cmd    (fish has no heredocs)
"$@"                                    $argv
Run bash code from fish:                bash -c 'export A=1; echo $A'      or the "bass" plugin to import bash environment files

Functions

function greet
    echo "Hello, $argv[1]!"
end
greet Alice
function mkcd --description 'make a directory and enter it'
    mkdir -p $argv[1]; and cd $argv[1]
end
funcsave mkcd                           # save it to ~/.config/fish/functions/mkcd.fish
funced mkcd                             # edit a function in your editor
functions                               # list functions          functions mkcd    show its code
functions -e mkcd                       # erase it
functions --copy ls ls_original         # keep the old one before overriding it
type ll   or   command -v ll            # what is this command?
Argument handling: argparse '-v/verbose' 'n/name=' -- $argv
alias ll 'ls -l' creates a tiny function. Use abbr (below) for shortcuts you want to see and edit.

Abbreviations (better than aliases for interactive use)

abbr -a gs git status                   # type gs then Space or Enter: it expands to "git status" in place
abbr -a gco git checkout
abbr -a gp git push
abbr -a ll ls -lah
abbr -a --position anywhere -- -h --help       # expand anywhere in the command (fish 3.6+)
abbr --show                             # list them all          abbr -e gs   erase one
Put your abbr lines in config.fish (inside "if status is-interactive").
The history shows the full command, so you never wonder what an abbreviation meant.

Control flow and testing

if test -f ~/.bashrc
    echo "found"
else if test -d ~/bin
    echo "a directory"
else
    echo "nope"
end
test -e path      exists      test -f path    file       test -d path    directory
test -z "$s"      empty       test -n "$s"    not empty  test $a -gt 3   number greater
if set -q MYVAR; echo "set"; end                     # is a variable set?
for f in *.txt; echo $f; end
for i in (seq 1 5); echo $i; end                     # command substitution uses (...)
while read -l line; echo $line; end < file.txt
switch (uname)
    case Linux
        echo "Linux"
    case Darwin
        echo "macOS"
    case '*'
        echo "other"
end
begin; echo one; echo two; end | sort                # group commands
command ls                                            # bypass a function or alias of the same name

Strings and math (built in)

string length "hello"                   # 5
string upper hello                      # HELLO         string lower HELLO
string sub -s 2 -l 3 "abcdef"           # bcd
string replace "a" "b" "banana"         # bbnana        string replace -a "a" "b" "banana"     # bbnbnb
string replace -r '(\w+)@(\w+)' '$2 at $1' "me@host"       # regex replace
string match -r '\d+' "abc123"          # 123           string match -q "*.txt" $f     # quiet, use in if
string split "," "a,b,c"                # one per line   string join "-" a b c     # a-b-c
string trim "  hi  "                    string pad -w 10 hi                  string repeat -n 3 ab
math 5 + 3 * 2                          # 11            math "sqrt(16)"          math -s2 10 / 3     # 3.33

Command substitution, pipes and redirects

set files (ls)                          # a list, one item per line
set today (date +%F)
echo "Today is $today"
ls | grep txt | wc -l
command > out.txt         >> out.txt (append)         2> err.txt         &> both.txt        &> /dev/null
command1 &| command2                    # pipe both stdout and stderr
command &                               # run in the background      jobs     fg    bg    disown
Wildcards: *  ?  ** (recursive, for example: ls **.md)      Braces: echo file{1,2,3}.txt      Home: ~

History

history                                 # show history       history | head -20
history search git                      # search for "git"     history search --contains docker
history delete --contains password      # remove entries (asks first)      history clear    erase all
history merge                           # pull in history from other running fish sessions

Directories and navigation

cd -                                    # go back to the previous directory
prevd / nextd                           # step through directory history (Alt+Left / Alt+Right)
dirh                                    # show the directory history
cdh                                     # pick from recent directories
pushd / popd / dirs                     # directory stack
prompt_pwd                              # the shortened path used in the prompt
Alt+Left / Alt+Right                    move backward and forward through visited directories
The "z" plugin (below) adds fast "jump to a frequent folder" navigation.

Plugins with Fisher

curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
fisher install PatrickF1/fzf.fish       # fuzzy search for history (Ctrl+R), files (Ctrl+Alt+F), git and processes
fisher install jethrokuan/z             # jump to frequent directories:   z projects
fisher install IlanCosman/tide          # a fast, configurable prompt
fisher install edc/bass                 # run bash scripts and import their environment: bass source ~/.nvm/nvm.sh
fisher list                             # installed plugins       fisher update    update them      fisher remove NAME
Review a plugin's source before installing. Plugins run code in your shell.
Other managers: Oh My Fish (omf) and fundle. Fisher is the lightest and most popular.

Prompt

The prompt is a function you can override:
function fish_prompt
    set -l last_status $status
    set_color cyan; echo -n (prompt_pwd)
    set_color normal
    if test $last_status -ne 0; set_color red; end
    echo -n ' > '
    set_color normal
end
funcsave fish_prompt
function fish_right_prompt; set_color brblack; date +%H:%M; set_color normal; end
Prompt helpers: fish_git_prompt (branch and status), fish_vi_mode_prompt, prompt_hostname, prompt_pwd
Ready-made prompts: fish_config prompt choose   or plugins such as Tide and Starship.

Oh My Posh (or Starship) with fish

Add to ~/.config/fish/config.fish (inside "if status is-interactive"):
oh-my-posh init fish --config ~/.config/ohmyposh/config.json | source
Starship alternative:   starship init fish | source
A Nerd Font in your terminal is required for the icons.

Colors and themes

set_color red; echo "error"; set_color normal          # color text, then reset
set_color -o brgreen                                    # -o bold, -u underline, -b background
set -U fish_color_command blue --bold                   # change what commands look like
set -U fish_color_error red
set -U fish_color_autosuggestion brblack
set -U fish_pager_color_selected_background --background=brblack
set -S fish_color_command        show a color setting
fish_config theme show     preview themes       fish_config theme save     keep the current colors

Scripts

Fish scripts start with:   #!/usr/bin/env fish      then:   chmod +x script.fish
#!/usr/bin/env fish
for file in $argv
    echo "Processing $file"
end
fish script.fish arg1 arg2               # run without changing permissions
fish -c 'echo hello; date'               # run commands from the command line
fish -n script.fish                      # syntax check only (does not run it)
fish_indent -w script.fish               # auto-format a script
Keep system scripts, cron jobs and CI files in bash or sh (#!/bin/bash). Use fish scripts only for your own tools.

Practical one-liners

# Add a folder to PATH permanently and safely
fish_add_path ~/.local/bin

# Create and enter a directory
mkdir -p ~/projects/new; and cd ~/projects/new

# Loop over files and rename
for f in *.jpeg; mv $f (string replace .jpeg .jpg $f); end

# Kill a process by name
pkill -f myapp     or     kill (pgrep myapp)

# Repeat the last command with sudo
sudo $history[1]        or press Alt+S

# List the 10 biggest files below here
du -ah . | sort -rh | head -10

# Show which shell you are in
echo $FISH_VERSION; echo $SHELL

# Use an environment file from bash
bass source ~/.env.sh        # needs the bass plugin      or:  export (cat .env | string split '\n')

# Time a command
time sleep 2

# Set an environment variable for the whole system's fish sessions
set -Ux MY_TOOL_HOME /opt/tool        # -U universal, -x exported, persists across restarts

# Reset fish to defaults (careful: removes your settings)
mv ~/.config/fish ~/.config/fish.backup

Common problems

"Unsupported use of '='. In fish, please use 'set VAR value'"   You typed a bash-style assignment: use set VAR value
"$(...)" or "&&" errors in old versions   Update fish. fish 3.4+ accepts $(...) and 3.0+ accepts && and ||
Command from a website fails in fish     It is written for bash. Run it as: bash -c '...'   or paste it into a bash session
"source: Error while reading file"       You sourced a bash script in fish. Use bass source file.sh, or run it in bash
PATH is wrong or has duplicates          Do not set PATH in several places. Use fish_add_path once, and check with: echo $PATH | tr ' ' '\n'
Old variable keeps coming back           A universal variable (set -U) overrides. Remove it: set -e -U NAME, and check with set --show NAME
"chsh: PAM: Authentication failure"      Enter YOUR password, and make sure fish's path is listed in /etc/shells
"chsh: fish is an invalid shell"         Add the fish path to /etc/shells (see Make fish your default shell)
Prompt icons are boxes                   Install a Nerd Font and select it in your terminal
Slow startup                             Too much in config.fish or many plugins. Time it: time fish -i -c exit    then trim
Aliases with arguments do not work       Write a function instead of an alias for anything that needs arguments
Scripts with #!/bin/bash behave normally Right: the shebang decides, your login shell does not matter
Autosuggestion is annoying               Turn it off: set -U fish_autosuggestion_enabled 0
Tab completion missing for a tool        Run fish_update_completions, or install the tool's completions file into ~/.config/fish/completions
Copy/paste of multi-line bash breaks     Fish handles multiple lines differently: paste into a file and run it with bash

Uninstall or go back to bash

chsh -s /bin/bash                        # back to bash, then log out and in
sudo apt remove fish                     # remove the program
rm -r ~/.config/fish                     # remove your fish settings

Tips

    Start fish from bash first (just type fish) and try it for a few days before making it your login shell.

    Keep bash as the login shell if you manage servers, and launch fish from ~/.bashrc so scripts and recovery still work.

    Learn three fish habits: set -x instead of export, end instead of fi/done, and (command) instead of $(command).

    Use abbr instead of alias for shortcuts, and use a function whenever you need arguments.

    Use fish_add_path for PATH changes, and set -U (universal variables) for settings you want everywhere.

    Press Right Arrow to accept a suggestion and Alt+S to add sudo. These two save the most typing.

    Write scripts in bash or sh unless you are the only user. Fish scripts are not portable to other machines.

    For commands copied from the internet, run them with bash -c '...' or in a bash session.

    Add a Nerd Font and Oh My Posh, Starship or Tide for a rich prompt, but check startup speed with time fish -i -c exit.

    Read the docs at fishshell.com/docs/current and run help inside fish. The tutorial is short and very good.