Fish Shell Install Guide and Cheat Sheet / survival guide
▶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.