Shell reference

Fish Shell Cheat Sheet

A practical reference for bash and zsh users switching to fish: syntax, commands, configuration, completions, scripting patterns, and the main POSIX-shell differences that cause shell shrapnel.

For bash/zsh users Interactive shell 16 sections

1. Mental model

What fish is

fish is an interactive shell with friendly defaults: autosuggestions, syntax highlighting, rich completions, universal variables, and a cleaner scripting syntax.

It is excellent as a daily interactive shell.

What fish is not

fish is not POSIX-compatible. Most bash and zsh scripts do not run unchanged in fish. Treat fish syntax as its own dialect, not a cosmetic bash variant.

Rule of thumb

Use fish interactively. Keep portable scripts in sh, bash scripts in bash, and fish scripts in files starting with:

#!/usr/bin/env fish

2. Basic command syntax

Taskfishbash/zsh equivalent
Run a commandls -lals -la
Separate commandsecho one; echo twoecho one; echo two
Command substitutionecho (pwd)echo $(pwd)
Use variableecho $nameecho $name
Set variableset name Alexname=Alex
Export variableset -x EDITOR vimexport EDITOR=vim
Erase variableset -e nameunset name
Exit status$status$?
Important: fish does not use VAR=value command, $(...), [[ ... ]], for x in ...; do ...; done, or export FOO=bar syntax.

3. Variables

Set variables

set name Alex
set count 42
set empty ""
set words alpha beta gamma

No =. Values are separated by spaces. Multiple values make a list.

Scope flags

set -l tmp value       # local to current block/function
set -g theme dark      # global to current fish process
set -U theme dark      # universal, persisted across sessions
set -x EDITOR nvim     # exported environment variable
set -gx PATH /opt/bin $PATH

Erase and inspect

set -e name            # erase variable
set -q name            # test whether variable exists
echo $name
set --show name        # detailed variable info

Lists and indexing

set fruits apple banana cherry

echo $fruits          # apple banana cherry
echo $fruits[1]       # apple, fish uses 1-based indexes
echo $fruits[2..3]    # banana cherry
echo $fruits[-1]      # cherry

set fruits[2] pear    # replace item 2
set -a fruits mango   # append
set -p fruits lime    # prepend

Strings

set greeting "hello world"
echo $greeting        # one argument because it is one variable element
string length $greeting
string replace hello hi $greeting
string split / $PATH

4. Expansion, quoting, and substitution

Command substitution

set here (pwd)
echo "Current directory: $here"

# Multiple lines become separate arguments by default
for file in (find . -name '*.go')
    echo $file
end

Variable expansion

set dir /tmp
echo $dir/file.txt        # /tmp/file.txt

set name world
echo hello_$name          # hello_world
echo "hello $name"        # hello world

Braces and globs

echo file.{txt,md}        # brace expansion
ls *.fish                 # globbing
ls **.fish                # recursive glob, fish-specific
No word splitting surprise: fish does not split variables on whitespace the way POSIX shells do. A variable element containing spaces remains one argument. This removes a whole class of bash bugs.

5. Conditionals and tests

if / else

if test -f config.toml
    echo "config exists"
else if test -d config.toml
    echo "config.toml is a directory"
else
    echo "missing"
end

test shortcuts

Checkfish
File existstest -e file
Regular filetest -f file
Directorytest -d dir
String equalstest "$a" = "$b"
String not emptytest -n "$a"
Integer greater thantest $n -gt 10
Command availablecommand -q git

Combining commands

command -q git; and echo "git found"
test -f README.md; or echo "no README"

if command -q rg; and test -d .git
    echo "ripgrep in a git repo"
end

fish supports and, or, and not instead of relying mainly on &&, ||, and !. Modern fish versions also accept && and ||, but the native words are idiomatic.

6. Loops

for

for file in *.txt
    echo $file
end

for n in (seq 1 5)
    echo $n
end

while

while test (count *.log) -gt 0
    echo "logs exist"
    break
end

Read lines

cat urls.txt | while read -l url
    echo "fetching $url"
end

break and continue

for file in *
    test -d $file; and continue
    echo $file
end

7. Functions and aliases

Define a function

function mkcd
    mkdir -p $argv[1]
    cd $argv[1]
end

Arguments

function greet
    echo "Hello, $argv[1]"
    echo "All args: $argv"
end

Function arguments live in $argv. fish does not use $1, $2, $@, or $#.

Named arguments with argparse

function deploy
    argparse 'e/env=' 'dry-run' -- $argv
    or return

    set -q _flag_env; or set _flag_env staging

    if set -q _flag_dry_run
        echo "Dry-run deploy to $_flag_env"
    else
        echo "Deploying to $_flag_env"
    end
end

Aliases

alias ll='ls -lh'
alias gs='git status'

# Persist an alias by turning it into a function
funcsave ll

Save functions

functions                # list functions
functions mkcd          # show function body
funcsave mkcd           # save to ~/.config/fish/functions/mkcd.fish
funced mkcd             # edit function interactively

8. Pipes, redirects, and jobs

Taskfish
Pipe stdoutcmd1 | cmd2
Redirect stdoutcmd > out.txt
Append stdoutcmd >> out.txt
Redirect stderrcmd 2> err.txt
Redirect stderr to stdoutcmd 2>&1
Redirect both stdout and stderrcmd &> all.txt
Background joblong-command &
List jobsjobs
Bring job to foregroundfg
Resume in backgroundbg

Exit status

false
echo $status            # 1

true | false
echo $pipestatus        # statuses of pipeline commands

9. Common and useful fish commands

Help and discovery

help                    # open fish help
help set                # help for command
man fish                # manual
fish --version
fish_config             # web UI for config/theme/functions

Variables

set                     # list variables
set --show PATH
set -q VAR              # exists?
set -e VAR              # erase
set -Ux NAME value      # universal exported var

Path management

fish_add_path ~/.local/bin
fish_add_path /opt/homebrew/bin
contains /opt/bin $PATH; or fish_add_path /opt/bin

Functions

functions
functions name
funced name
funcsave name
functions -e name       # erase function

Completions

complete -c mycmd       # show completions
complete -c mycmd -e    # erase completions
fish_update_completions # generate from man pages

History

history
history search git
history delete --exact "bad command"

Prompt

fish_config prompt      # choose prompt interactively
funced fish_prompt
funcsave fish_prompt

Abbreviations

abbr -a gco git checkout
abbr -a gst git status
abbr -a k kubectl
abbr --show
abbr -e gst

Abbreviations expand as you type. They are often better than aliases because the final command is visible and editable.

10. Configuration files and startup

PathPurpose
~/.config/fish/config.fishMain user config, similar in spirit to .zshrc or .bashrc.
~/.config/fish/functions/*.fishAutoloaded function files. One function per file is the usual pattern.
~/.config/fish/completions/*.fishUser completions.
~/.config/fish/conf.d/*.fishSmall config snippets loaded automatically.
~/.config/fish/fish_variablesUniversal variables. Usually edited via set -U, not manually.

Typical config.fish

# ~/.config/fish/config.fish

# Only run interactive config in interactive shells
if status is-interactive
    fish_add_path ~/.local/bin
    fish_add_path /opt/homebrew/bin

    set -gx EDITOR nvim
    set -gx VISUAL nvim

    abbr -a gst git status
    abbr -a gco git checkout
    abbr -a k kubectl
end

Login shell

# Check path to fish
command -v fish

# Add fish to allowed shells if needed
# Example path may vary:
echo /opt/homebrew/bin/fish | sudo tee -a /etc/shells

# Change login shell
chsh -s /opt/homebrew/bin/fish

11. Completions

fish completions are first-class and often more readable than bash completion scripts.

Simple completion

complete -c mytool -s h -l help -d "Show help"
complete -c mytool -s v -l verbose -d "Verbose output"
complete -c mytool -l env -a "dev staging prod" -d "Environment"

Completion from command output

complete -c killbyname -a "(ps -axo comm | sort -u)" -d "Process name"

Completion files

~/.config/fish/completions/mytool.fish

12. Scripting patterns

Script skeleton

#!/usr/bin/env fish

set -l script_dir (dirname (status filename))

function usage
    echo "usage: myscript <name>"
end

if test (count $argv) -lt 1
    usage
    exit 1
end

set -l name $argv[1]
echo "Hello, $name from $script_dir"

Status helpers

status is-interactive   # true if interactive shell
status is-login         # true if login shell
status filename         # current script/function file
status dirname          # directory of current script/function file

Error handling

mkdir -p build
or begin
    echo "failed to create build" >&2
    exit 1
end

command -q git
or begin
    echo "git is required" >&2
    exit 1
end

Temporary environment for one command

# bash/zsh:
# FOO=bar command

# fish:
env FOO=bar command

# or:
begin
    set -lx FOO bar
    command
end

13. Main differences from bash/zsh

Conceptbash/zshfishNotes
POSIX compatibilitysh-likeNot POSIXDo not expect shell scripts to run unchanged.
Set variablex=1set x 1No equals sign.
Exportexport x=1set -x x 1Use set -gx for global exported values.
Temporary envx=1 cmdenv x=1 cmdOr use a begin block with set -lx.
Command substitution$(cmd)(cmd)Parentheses, not dollar-parens.
Exit code$?$statusPipeline statuses in $pipestatus.
Function args$1 $2 $@ $#$argv[1] $argv[2] $argvFish lists are 1-indexed.
Blocksif ...; then ... fiif ...; ...; endAll major blocks close with end.
Loopsfor x in y; do ...; donefor x in y; ...; endNo do or done.
Conditionals[[ ... ]]test ...Use test, string, or command status.
Logical operators&&, ||, !and, or, notNative fish style is word-based.
Subshell grouping(cd dir; cmd)begin; cd dir; cmd; endbegin does not create a subshell by itself.
Arraysarr=(a b)set arr a bAll variables can be lists.
IndexingUsually 0-based in bash arrays1-based$arr[1] is first element.
Word splittingVariables split unless quotedNo automatic whitespace splittingLess quoting anxiety, fewer footguns.
Here docscat <<EOFNo traditional heredocsUse printf, string join, or external files.
AliasesAlias syntaxAliases are wrapper functionsalias exists, but functions and abbreviations are often better.

14. Everyday recipes

Append to PATH safely

fish_add_path ~/.local/bin

Set default editor

set -Ux EDITOR nvim
set -Ux VISUAL nvim

Run command if available

if command -q rg
    rg TODO
else
    grep -R TODO .
end

Make a project shortcut

function cproj
    cd ~/src/my-project
end
funcsave cproj

Find files and loop

for file in (find . -name '*.json')
    jq . $file >/dev/null
    or echo "invalid: $file"
end

Prompt confirmation

read -l -P "Deploy to prod? [y/N] " answer
if test "$answer" = y
    echo "deploying"
end

Count arguments

if test (count $argv) -eq 0
    echo "no args"
end

Capture command output

set branch (git branch --show-current)
echo "branch: $branch"

15. Migration tips for bash/zsh users

  1. Do not paste your entire .bashrc or .zshrc into config.fish. Port it deliberately.
  2. Start with PATH, editor, and abbreviations. Most of the value comes quickly.
  3. Use fish_add_path instead of manually rebuilding PATH.
  4. Prefer abbr for command shortcuts. It expands visibly, so your history contains the real command.
  5. Use functions for logic. They are cleaner than aliases for anything with arguments.
  6. Keep existing scripts as-is. Run bash scripts with bash, zsh scripts with zsh, and fish scripts with fish.
  7. Remember the big five syntax swaps: set for assignment, (cmd) for substitution, $status for exit status, $argv for function args, and end for closing blocks.

16. Mini reference

Variables

set x value
set -l x value
set -g x value
set -U x value
set -x x value
set -gx x value
set -e x
set -q x

Blocks

if test ...
    ...
else if test ...
    ...
else
    ...
end

for x in ...
    ...
end

while test ...
    ...
end

switch $x
    case a
        ...
    case '*'
        ...
end

Functions

function name
    echo $argv
end

funcsave name
funced name
functions name

Commands

help
fish_config
fish_add_path
abbr
complete
history
status
string
math
argparse