Fish Shell Cheatsheet - Functions & Aliases

Fish ships with highlighting, completion and fuzzy history search enabled out of the box, so the gap to Bash is less about features and more about zero setup. The real cost of switching is syntax. Blocks close with end instead of fi or done, variables are set rather than exported, list indexing starts at 1, and string handling leans on command substitution where Bash uses pipes. Interaction is mostly muscle memory, but scripts are where migrations break, because .bashrc fragments copied verbatim tend to fail in confusing ways. This sheet is organised as a translation table: variable scope, control flow, functions and abbreviations, string and pipeline idioms, configuration and themes, then history and key bindings. Use it to convert a Bash habit into its Fish equivalent instead of guessing, or to decide when wrapping the command in sh -c is the saner option. Anything with a shebang and strict POSIX assumptions still belongs in /bin/sh rather than being ported, so judge per script instead of per shell.

SysOps·36 commands·Last updated 2026-07-21

Typical Use Case

For developers who switched to fish and kept writing bash. fish is not a bash with different prompt settings: variables are set with a command rather than an assignment, there is no process substitution, and lists splice instead of word-splitting. Migrating by copying your .bashrc mostly fails. This sheet covers the syntax differences that actually bite, the three variable scopes, history and search, string manipulation, and plugin installation with fisher.

Variables & Scope 7

set name "John"
Set a variable
echo $name
Use a variable
set -x PATH /usr/local/bin $PATH
Set an env var (export)
set -U fish_user_paths /usr/local/bin $fish_user_paths
Set a universal var (persistent)
set -l local_var "value"
Local var (current function only)
set -g global_var "value"
Global variable
set -e name
Erase a variable

Control Flow 5

if test -f file.txt echo "exists" end
Conditional (file exists)
if test $count -gt 5 echo "many" else echo "few" end
if-else branch
for file in *.txt echo $file end
Loop over files
while read -l line echo $line end < file.txt
Read a file line by line
switch $status case 0 echo "success" case "*" echo "failed" end
Multi-branch switch

Functions & Aliases 7

function greet echo "Hello $argv" end
Define a function ($argv args)
funcsave greet
Save a function to the config
functions
List all functions
functions greet
Show a function definition
alias cls="clear"
Create an alias
abbr -a gco "git checkout"
Create an abbreviation (auto-expands)
funced greet
Edit a function interactively

Strings & Pipes 6

string length "hello"
Get string length
string split "," "a,b,c"
Split a string by delimiter
string join ";" "a" "b" "c"
Join strings with a delimiter
string replace "old" "new" "hello old"
Replace a substring
string match -r "^\d+" "123abc"
Regex match
echo "data" | string upper
Pipe to uppercase

Config & Theme 6

fish_config
Configure theme/prompt via web UI
fish_config theme choose Nord
Switch theme from CLI
set -U fish_greeting ""
Disable the greeting
fish_update_completions
Update completion scripts
cat ~/.config/fish/config.fish
View the config file
fisher install jorgebucaran/autopair.fish
Install a plugin with fisher

History & Keys 5

history
View command history
history search "git"
Search history
history delete "rm -rf"
Delete a history entry
Ctrl+R
Incremental history search
Alt+← / Alt+→
Move cursor by word

Parameter matrix

ParameterEffectExample
set -x / -g / -U / -l / -eExported (environment), global, universal (persistent across sessions), local, and eraseset -U fish_user_paths /usr/local/bin $fish_user_paths
fish_greetingThe login banner; setting it to an empty string removes the greetingset -U fish_greeting ""
abbr -aCommand abbreviation: expands on typing and stays out of history, better than alias for commandsabbr -a gco "git checkout"
stringBuilt-in string toolkit that replaces bash cut/sed/tr/grep pipelinesstring split "," "a,b,c"
funcsavePersist a function you just defined interactively into the config filefuncsave greet

Common pitfalls

SymptomMoving export PATH=... from .bashrc into fish leaves PATH unchanged, or adds a literal string to it.

Causeexport VAR=value is not valid fish syntax; fish assigns with the set command and flags.

FixUse set -x PATH /usr/local/bin $PATH, or set -U fish_user_paths /usr/local/bin $fish_user_paths to make it persistent.

SymptomA function you defined interactively is gone after closing the terminal.

Causefish never writes interactively defined functions back to the config file on its own.

FixRun funcsave greet after defining it; edit an existing one with funced greet.

SymptomStartup reports "fish: Unknown command: [" followed by a pile of syntax errors.

CauseBash [ ] conditional syntax was pasted in; fish uses test plus end blocks instead.

FixRewrite as native fish blocks such as if test $count -gt 5 / else / end, converting piece by piece.

SymptomDisabling an alias with "alias name" fails saying the function does not exist.

Causefish aliases are functions, so unalias has no equivalent and removal uses functions -e.

FixUse functions -e name; for command substitutions prefer abbr -a, which expands more cleanly.

SymptomA PATH entry you added manually disappears after restarting the terminal.

Causeset -x only affects the exported variable for this session and never reaches persistent storage.

FixUse set -U fish_user_paths /usr/local/bin $fish_user_paths so it survives across sessions.

Troubleshooting

  1. 1Check whether the config actually took effect

    cat ~/.config/fish/config.fish

    fish reads only this file (not .bashrc); reload with source ~/.config/fish/config.fish after editing.

  2. 2Find a long command you ran before

    history search "git"

    The non-interactive twin of Ctrl+R incremental search; history delete "rm -rf" removes risky entries.

  3. 3Inspect a function or alias definition

    functions && functions greet

    functions lists everything, functions greet prints one definition - enough to spot a shadowed name.

  4. 4Install a plugin

    fisher install jorgebucaran/autopair.fish

    fisher is the fish plugin manager; afterwards run functions to confirm the plugin loaded.

Command Examples

Export an environment variable and read it back

set -x PATH /usr/local/bin $PATH

The -x flag is the export behaviour that child processes inherit, and this is the fish replacement for the bash export PATH=... form.

Add a directory to PATH permanently

set -U fish_user_paths /usr/local/bin $fish_user_paths

Universal variables are written to disk and apply to every future session, and running this repeatedly does not duplicate the entry.

Split a string on commas

string split "," "a,b,c"

string is the built-in text toolset, and it covers the jobs you would otherwise chain through cut, sed, tr and grep.

Create a command abbreviation instead of an alias

abbr -a gco "git checkout"

Type the prefix and hit space to expand it, and unlike alias it does not pollute your history, which makes it the better fit for command shorthands.

Common Pitfalls

  • Never paste the bash export PATH=... form into fish; that syntax does not exist there, so you end up with a literal in PATH and no directory added.
  • Every assignment in fish goes through the set command, because the VAR=value form never creates a variable.
  • if, for, while, function and switch must all be closed with end; carrying over fi, done or esac from bash is a hard syntax error.
  • A function typed at an interactive prompt is not saved automatically, so run funcsave or the work is lost at the next prompt.
  • To make a PATH change survive, write it to fish_user_paths with set -U; a plain set -x disappears the moment the terminal closes.

Tips

  • Fish's if/for/function/switch end with end (no fi/done/esac).
  • set -U sets a universal var, persisted and shared across sessions.
  • Fish has autocompletion out of the box; Tab to use, Alt+Right to accept.
  • fisher is Fish's plugin manager: curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher.
  • abbr is lighter than alias and auto-expands to the full command.
  • Fish isn't POSIX-compatible; can't run bash scripts directly, but is more intuitive.

FAQ

Why does export PATH=... from my .bashrc do nothing once I paste it into fish?

fish does not support the VAR=value assignment form and has no export builtin, so it treats the whole line as a command name, fails to find it, and leaves a literal sitting in your PATH. The fish spelling is set -x PATH /usr/local/bin $PATH, where -x marks the variable for export and references use a dollar sign in front of the name.

Why do fish conditionals and loops not need fi or done?

fish uses one closing keyword for every block: if, for, while, function and switch are all terminated with end, so there is no matching fi, done or esac. When you port a bash script and a for loop appears to have no done, this single rule is the reason.

The directory I added to PATH disappears every time I open a new terminal. Why?

It only ever existed in the current session and was never persisted. Use set -U fish_user_paths /usr/local/bin $fish_user_paths to record the directory as a universal variable: it is written to configuration, survives across sessions, and repeated runs do not pile up duplicate PATH entries. The PATH tab in fish_config edits the same thing through a UI.

Functions I define interactively vanish when I close the terminal. How do I keep them?

An interactively defined function lives only in the current session unless you save it. After defining one, run funcsave greet and the body is written to your configuration so it survives a restart. If you need to rewrite it, edit it interactively with funced greet, then funcsave again, otherwise the fix is lost just like the original.

What do the -l, -g, -U and -x flags of set actually control?

All four are scope switches on set. -l creates a local variable visible only inside the current function or block. -g creates a global shared by the session. -U creates a universal variable written to configuration and preserved across sessions. -x exports the value so child processes inherit it. Use -l inside functions to avoid polluting the global scope, -x for values external commands must read, and -U for anything that has to survive a new terminal.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us