Skip to content
Tips and recipes for fish, from shell to plate
Branch: master
Clone or download
Type Name Latest commit message Commit time
Failed to load latest commit information. Fix typos and grammar errors Feb 23, 2019

The Fish Cookbook

This document is a living book of recipes to solve particular programming problems using fish-shell. Whether you are in the mood for mackerel or salmon on the grill, there is always a distinctive and delicious way to prepare any type of fish.

Licensed CC BY-NC-SA 4.0

Table of Contents


Well-known shells are bash, ash, csh, ksh and the popular zsh. All these shells are POSIX, so well-written POSIX-compliant scripts should run without modification in any of them. That's about the only good reason to learn POSIX shell.

Fish is not a POSIX shell. Your bash scripts will not run in fish without some modification.

make && make install

will error with: "Unsupported use of '&&'. In fish, please use 'COMMAND; and COMMAND'."

That's easy to fix.

make; and make install

Here is a quote from the fish design document:

Fish should be user-friendly, but not at the expense of expressiveness. Most tradeoffs between power and ease of use can be avoided with careful design.


How to install fish?

You can find directions in the official website or follow the instructions provided here for your OS.

macOS with homebrew
brew update && brew install fish
apt-key add - < Release.key
echo 'deb /' >> /etc/apt/sources.list.d/fish.list
apt-get update
apt-get install fish
sudo apt-add-repository ppa:fish-shell/release-2
sudo apt-get update
sudo apt-get install fish
cd /etc/yum.repos.d/
yum install fish
cd /etc/yum.repos.d/
yum install fish
Arch Linux
pacman -S fish
emerge fish
From source
sudo apt-get -y install git gettext automake autoconf ncurses-dev build-essential libncurses5-dev

git clone -q --depth 1
cd fish-shell
autoreconf && ./configure
make && sudo make install

How to make fish my default shell?

Once you have installed fish and it's somewhere in your $PATH, e.g. /usr/local/bin, you can make it your default login shell.

echo /usr/local/bin/fish | sudo tee -a /etc/shells
chsh -s /usr/local/bin/fish

How to find out where fish is installed?

Use which.

which fish

Getting Started

How to learn fish?

The best way to learn fish is to read the official documentation and tutorial.

Where to ask for help?

What's a prompt and what are all these ugly characters?

The prompt is where you type commands and interact with your shell interpreter, e.g., fish. Read more about the UNIX prompt here.

Maybe it looks like this:

x@mbp ~/C/fish-shell>

The tilde ~ is an abbreviation of the home directory, for example /users/x/home, /Users/x, etc.

The @ means at. I can see x, my user, is logged into mbp, which is the name I gave to my workstation.

The forward slash / is the path delimiter. At a glance, I can see the current directory is in the vicinity of ~, somewhere inside C/fish-shell. The C is the first letter of the parent directory, Code in my case.

As of fish >=2.3, you can customize the length of the abbreviated path.
set fish_prompt_pwd_dir_length NUMBER


set fish_prompt_pwd_dir_length 0

for no abbreviations.

x@mbp ~/Code/fish-shell

The greater-than symbol > is used here to indicate the end of the prompt.

You don't like these conventions? Create your own prompt the way you want it.

See How to create my own prompt in fish?

How to find my current location in fish?

You can find out where you are via the read-only environment variable $PWD.

echo $PWD

Another way to find out the current directory is via the pwd builtin.


In fish, both $PWD and pwd always resolve symbolic links. This means that, if you are inside a directory that is a symbolic reference to another, you still get the path to the real directory.

Interactively, pwd is easier to type. For scripting, $PWD is a function call less expensive.

set -l cwd (pwd)
echo "The current working directory is $cwd"

# Versus

echo "The current working directory is $PWD"

How to find and run commands in fish?

To run a command type the name of the command and press return.


Or, start typing the command you are looking for, and press tab. Fish will use the builtin pager which you can browse and select the command interactively.

Fish knows what commands are available by looking at the $PATH environment variable. This variable contains a list of paths, and every binary file inside any of those paths can be run by their name.

Print your $PATH contents.

printf "%s\n" $PATH

or list every command in your system and display them in columns.

ls $PATH | column

If the list is truncated, use:

ls $PATH | column | less

Use k and j to navigate the list down / up, and q to exit.

The $PATH variable is created at the start of the fish process during the environment initialization. You can modify, prepend or append to this variable yourself, e.g., in ~/.config/fish/

Similar to the type, builtin and functions builtins previously introduced, *nix systems often include one or more shell-agnostic alternatives, e.g., which, apropos, whatis, etc.

These commands overlap in functionality, but also possess unique features. Consult your system's manpage for details.

How to check if a command succeeded in fish?

Every command returns an exit code to indicate whether they succeeded or not. An exit code of 0 means success. Anything else means failure. Different commands use different integers to represent what errors can happen.

You can check the exit code of any command using the read-only variable $status.

echo $status

What is the fish shebang?

There is a type of comment known as the shebang used to tell the shell system to run a program using the path of your script as an argument. The shebang is always written at the beginning of the script.

To run a script with fish use a shebang like so:

#!/usr/bin/env fish
#!/usr/bin/env fish

if status --is-interactive
    echo "We live in an interactive world!"

Save that to a file and mark it as executable.

chmod +x my_script

The system above allow us to run the script directly by using its path


instead of

fish my_script


How to set environment variables in fish?

Use the set builtin.

set foo 42

The set builtin accepts the following flags to explicitly declare the scope of the variable:

  • -l, --local: available only to the innermost block
  • -g, --global: available outside blocks and by other functions
  • -U, --universal: shared between all fish sessions and persisted across restarts of the shell
  • -x, --export: available to any child process spawned in the current session

If no scope modifier is used, the variable will be local to the current function; otherwise, it will be global.

If the variable has already been defined, the previous scope will be used.

Local Variables

The variable foo will not be available outside of the if block.

if true
    set -l foo 42

echo "foo=$foo" # foo=
Global Variables

The variable foo will be available outside the if block.

if true
    set -g foo 42

echo "foo=$foo" # foo=42
Universal Variables

The variable foo will be preserved and available to future shell sessions.

set -U foo 42
echo "foo=$foo" # foo=42
Exported Variables

The variable foo will be local and exported, therefore available to the fish child process created inside the if block.

if true
    set -lx foo 42
    fish -c 'echo "foo=$foo"' # foo=42

The variable foo will be global, but since it's not exported, it won't be available to the fish child process.

set -g foo 42
fish -c 'echo "foo=$foo"' # foo=

The variable GPG_AGENT_INFO will be universal and exported, therefore preserved across future shell sessions and child processes.

set -Ux GPG_AGENT_INFO /Users/x/.gnupg/S.gpg-agent:12345:2

How to export a variable in fish?

Use the set builtin and the scope modifier -x or --export.

set -x foo 42
fish -c 'echo "foo=$foo"' # foo=42

How to list all environment variables in fish?

Use the set builtin without any modifier flags.


To print only the variable names, without the values, use --name.

set --names

To not truncate long lines use --long.

set --long

How to set the $PATH persistently in fish?

The correct way to persistently add a path to your $PATH is using fish $fish_user_paths variable.

set -U fish_user_paths $fish_user_paths my_path

See $PATH in the fish tutorial for more information.

How to remove a path from the $PATH in fish?

Use the set builtin with the -e or --erase flag in combination with the contains builtin to find the index of the path you want to remove.

if set -l index (contains -i $my_path $PATH)
    set -e PATH[$index]

How to check if a path exists in the $PATH in fish?

Use the contains builtin.

if contains $my_path $PATH
    # $my_path is in $PATH


How to create a function in fish?

Use the function builtin.

function mkdirp
    mkdir -p $argv

To make this function available in future fish sessions save it to ~/.config/fish/functions/ A clean way to accomplish this is using the funcsave function.

funcsave mkdirp

Alternatively, you can use the functions builtin to write the function definition to a file.

functions mkdirp > ~/.config/fish/functions/

How to create a private function in fish?

You can't. In fish, functions are always public.

As a workaround, use a custom namespace to prefix any function you want to treat as private.

function _prefix_my_function

It's not impossible to simulate private scope using functions -e.

function foo
    function _foo
        echo Foo
        functions -e _foo # Erase _foo

Should function names and file names match?

Yes. The lazy-loading / autoloading mechanism relies on this convention to work.

If you have a file ~/.config/fish/functions/ with a valid function definition bar:

  1. In a new shell, trying to run bar produces an unknown-command error.
  2. Typing foo will highlight as a valid command, but produce an unknown-command error.
  3. Trying to run bar again now works as intended.

Save bar to ~/.config/fish/functions/

function bar
    echo Bar
functions bar > ~/.config/fish/functions/

Create a new shell session.


Try to run bar, then foo, then bar again.

# fish: Unknown command 'bar'
# fish: Unknown command 'foo'
# Bar

Can I define more than one function in a file?

Yes, you can. Note that fish does not have private functions, so every function in the file ends up in the global scope when the file is loaded. Functions are eagerly loaded as well, which it's not as effective as using one function per file.

How to show the definition of a function in fish?

If you know the command is a function, use the functions builtin.

functions my_function

If you are not sure whether the command is a function, a builtin or a system command, use type.

type fish
fish is /usr/local/bin/fish

What's the difference between functions, builtins, and commands in fish?

System commands are executable scripts, binaries or symbolic links to binaries present in your $PATH variable. A command runs as a child process and has only access to environment variables which have been exported. Example: fish.

Functions are user-defined. Some functions are included with your fish distribution. Example: eval.

Builtins are commands compiled with the fish executable. Builtins have access to the environment, so they behave like functions. Builtins do not spawn a child process. Example: functions.

How do I list the functions defined in fish?

Use the functions builtin without arguments.

The list will omit functions whose name start with an underscore. Functions that start with an underscore are often called hidden. To show everything, use functions -a or functions --all.

Alternatively, launch the fish Web-based configuration and navigate to the /functions tab.

fish_config functions

How to check if a function exists in fish?

Use the type function to query information about commands, builtins or functions.

if not type --quiet "$command_name"
    exit 1
Use builtin --names to query builtins.
if not contains -- "$command_name" (builtin --names)
    exit 1
Use functions --query to check if a function exists.
if not functions --query "$command_name"
    exit 1
Use command --search for other commands.
if not command --search "$command_name" > /dev/null
    exit 1

Easier in fish >= 2.5

if not command --search --quiet "$command_name"
    exit 1


How to access the arguments passed to a function in fish?

Use the $argv variable.

function Foo
    printf "%s\n" $argv

Foo foo bar baz

How to access the arguments passed to a script in fish?

Use the $argv variable. Pass the arguments when running the script.

fish ./my_script foo bar baz
Example: my_script
#!/usr/bin/env fish
printf "%s\n" $argv

How to parse command line arguments in fish?

Use a for loop.

for option in $argv
    switch "$option"
        case -f --foo
        case -b --bar
        case \*
            printf "error: Unknown option %s\n" $option

For a more complete CLI parsing solution, see getopts.


How to define an alias in fish?

Create a function and save it to ~/.config/fish/functions.

function rimraf
    rm -rf $argv

For backward compatibility with POSIX shells, use the alias function.

alias rimraf "rm -rf"

Avoid using alias inside ~/.config/fish/ See the next section.

What's wrong with aliases?

Aliases created with alias will not be available in new shell sessions. If that's the behavior you need, then alias is acceptable for interactive use.

To persist aliases across shell sessions, create a function and save it to ~/.config/fish/functions. This takes advantage of fish function lazy-loading / autoloading mechanism.

Using alias inside ~/.config/fish/ will slow down your shell start as each alias/function will be eagerly loaded.


Where's the .bash_profile or .bashrc equivalent in fish?

Your fish configuration is saved to ~/.config/fish/


How to read from a file in fish?

To read a file line by line, use the read builtin.

while read -la line
    echo $line
end < my_file

How to read from stdin in fish?

Use the read builtin.

read --prompt "echo 'Name: ' " -l name
Name: Marvin
echo $name

To read from an arbitrary input stream use read together with the while builtin.

while read -la line
    echo $line

How to redirect stdout or stderr to a file in fish?

Redirect stderr to $my_file.

my_command ^ $my_file


my_command 2> $my_file

Redirect stdout to $my_file.

my_command > $my_file

Redirect stdout to stderr.

my_command >&2

Redirect stderr to stdout.

my_command 2>&1


How to run a command in the background in fish?

Use &.

sleep 10 &

See also Background jobs in the fish documentation.

How to check if background jobs are running in fish?

Use the jobs builtin.

if jobs > /dev/null
    echo Busy

How to synchronize two or more background tasks in fish?

fish has no wait command, but you can write your own.

First, to check if tasks are running in the background, parse the output from the jobs builtin.

Parse by Job ID
function get_jobs
    jobs $argv | command awk -v FS=\t '
            jobs[++nJobs] = $1
        END {
            for (i in jobs) {
            exit nJobs == 0
Parse by Group ID
function get_jobs
    jobs -g | command awk 'NR > 0 { print; i++ } END { exit i == 0 }'

Then, block the foreground until all background jobs are finished.

function wait
    while true
        set -l has_jobs
        set -l all_jobs (get_jobs)
        or break

        for j in $argv
            if contains -- $j $all_jobs
                set -e has_jobs

        if set -q has_jobs
set -l urls "https://"{google,twitter,youtube,facebook,github}".com"

for url in $urls
    fish -c "curl -Lw \"$url: %{time_total}s\n\" -o /dev/null -s $url" &

wait (get_jobs)

How to wait for a background process in fish?

Fish has no wait builtin. To wait for a background process to finish, use the solution described in How to synchronize two or more background tasks in fish?.

You can’t perform that action at this time.