Fish Shell

24 minute read     Updated:

Anurag Kumar %
Anurag Kumar

This article introduces the Fish shell. Fish shell is pretty sweet. Guess what else is? Earthly. Earthly is a powerful tool to optimize your CI pipeline. Check it out.

What Is a Shell? Essentially, it’s the place where you execute all your terminal commands. A shell goes beyond this simple definition – and you can create sub-shells to run your scripts and whatnot – but from a user perspective, I would like to keep it simple and think of it as a place where you invoke all your commands using your keyboard via text input. There are many types of shells out there. Bash is one of the oldest, and it ships by default in most Linux distributions. There are other shells as well, and these shells were developed after the bash shell. Some of the more common ones include:

  1. zsh
  2. fish
  3. nushell
Image Credits: Behind ‘Hello World’ on Linux notes

Why Learn a New Shell?

One of the reasons I switched from bash to zsh and then later to fish is because I was feeling productive in fish shell and could do things faster. After using fish shell for a while, I didn’t want to go back to bash aside from scripting. Zsh - along with oh-my-zsh - is an outstanding shell, but replicating the same features on a remote instance was challenging. So I switched to fish shell and haven’t looked back.

The main advantage of fish shell is that it is easy to set up and use out of the box. The default behavior of fish shell is pretty good, and it’s extensible too, so that you can customize it to your needs. You can customize how your shell looks very quickly.

Introduction to Fish Shell

Fish shell is a friendly and interactive shell you can use for daily tasks. Besides being a shell, fish is a scripting language, which you can use to write and run scripts.

Installation and Setup

For Debian-based distros, you can install fish shell by running the following command.

sudo apt install fish

If you’re using a different distro, you can find the installation instructions.

Features of Fish Shell

Here are my favorite features, in order of impact they’ve had on my workflow.

Auto Suggestions out of the Box

Fish provides you with very smart autosuggestion as you type in the terminal. You can also auto-complete the suggested command using the right arrow.

Tab-Based Completion

Tab-based completion is also very powerful with fish shell. For example, I was done with working with ssh-agent, and I wanted to kill the process. Now I had to find the PID associated with the ssh-agent. One way to do that would be to use ps command in conjunction with grep, but with fish you can just type the name of the process.

kill ssh<tab>

Helpful Flag Options

Unix commands have a lot of flags, and it’s good because they give you a lot of flexibility. With time, it becomes hard to recall all the flags of each command. Fish generates descriptive completion considering man-pages into account. This gives you an excellent descriptive message for each command. For example, consider git command output.

git <tab>

Fish Syntax Highlighting

Syntax highlighting will give you colorful syntax and sometimes indications if you’re typing a wrong command or a correct command that doesn’t exist in the system now.


Setting Aliases

Setting aliases in fish shell is the same as setting aliases in bash. You can set aliases in fish shell by using alias command.

alias cat='bat -pP'

This is a simple alias that I use, and it gives me syntax highlighting when I use cat command. While this sounds similar to bash, I really like that the alias command in fish shell provides you the flag to save the alias permanently without opening your ~/.config/fish/ file. For example, You’re on a remote instance and want to set a permanent alias for kubectl command. You can do that by running the following command.

alias -s k kubectl

Under the hood, this command will create a function named k, which will be stored at ~/.config/fish/functions/

You can look at this function by invoking the following command.

$ type k
k is a function with definition
# Defined in /root/.config/fish/functions/ @ line 1
function k --wraps=kubectl --description 'alias k kubectl'
  kubectl $argv;

If you have a longer command for which you want to create an alias, you can enclose it within brackets.

alias -s kgp 'kubectl get pods'

Note that we are enclosing our command inside single quotes.

To view the git logs in a colorful manner, I use the following alias in my system.

alias glo 'git log --pretty=format:"%Cred%h%Creset \
-%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset" \

Setting Abbreviations

Abbreviations are unique to fish shell. To set abbreviations, you can use the following syntax. The recommended way is to add the abbreviations in your ~/.config/fish/ file.

abbr --add gst 'git status'

You can write a few keywords and hit space to complete the command. For example, you can write gst and hit space afterward. This will complete the command to git status. This way, your history will look cleaner when you’re traversing through your history to find any difficult command that you rarely execute sometimes.

Even if you don’t put a space, the command will expand itself first, and then run.


Setting Variables

In fish, You might need to set environment variables at some point in time. When you install go on a new system, you’re required to set the GOPATH environment variable. You can set the same in fish using the following command.

set -U GOPATH ~/go

You can use -l flag to set local environment variables, which will only persist till the life of the current shell.

set -l VAR foo

Private Mode

In fish shell, you can use fish -P or fish --private command to enter private mode. In private mode, your shell history will not be saved. You’ll also not get autosuggestion for the command you’ve invoked previously.

I use this whenever I need to share my terminal with someone during a meeting.

Sometimes, it’s also helpful when passing a token/secret to any command as an argument. For example, I’ll run the following command in private mode because this involves passing a secret password as an argument.

docker login -u randomuser -p randompassword

You can simply type exit on the terminal to move out of private mode.


Web-Based Customization

The easiest way to customize your fish shell if you want a GUI-based customization is web-based. Invoke fish_config or fish_config browse command via your terminal, and it’ll open up a page in your default browser. You can use this page to customize your fish shell.


GUI-based customization is good because it allows you to change colors and shows you how it looks on the console when modifying itself.

You can see that you can customize your color, prompt, functions, variables, history, and bindings.

CLI-Based Customization

Fish support customization through CLI. You can use fish_config command to customize your fish shell. To customize your prompt, you can use fish_config prompt show command to get all the prompts, and based on your choice, you can use fish_config prompt save <prompt-name> to save the prompt. You can also choose and update themes with the theme sub-command. For example, to show all the themes, you can use fish_config theme show


Functions in Fish

In every programming language, how you define functions are one of the most integral parts of the language. Fish is no different. You can write your own functions to use in your shell at the startup or by manually invoking them.

Builtin Functions

These functions come built-in with fish shell. You can see the list of built-in functions by running functions command.

$ functions 
# truncated output
N_, abbr, alias, bg, cat, cd, cdh, contains_seq, diff, dirh, dirs, \
disown, down-or-search, e, edit_command_buffer, export, fg, \
fish_add_path, fish_breakpoint_prompt, fish_clipboard_copy, \
fish_clipboard_paste, fish_command_not_found, fish_commandline_append,

Writing Custom Functions

You can write custom function in fish shell. I wrote a custom function to create a new directory and cd into it.

function mkcd 
    mkdir $argv && cd $argv

I often used this when I needed a temporary place to test something.

mkcd some_dir_name

There’s also an easy way to write this in fish shell. You can use the funced command to start writing a function and then save the function using the funcsave command.

Let’s see how it works with an example. Let’s write a function that prints the active connection that our system is connected to. Let’s call the function connection

Let’s start writing it.

funced conn

This will open up an editor in your system.

Note: You need to set the EDITOR environment variable or use the command funced conn -e nvim to open up the function in neovim.

You’ll have the following template ready with you.

function conn


Now you’ll have to fill this template to complete the function. You can do a lot of this here, using text manipulation to get output or a simple command that will show you something. Let’s use nmcli command to show all our device’s active network connections.

# Defined via `source`
function conn
    nmcli connection show --active

I’ve added a simple command showing us the active connection when you type conn on your terminal. To test it out, save the file and close the editor. You can type conn in your terminal to test the above function.

$ conn
NAME             UUID                                  TYPE      DEVICE
Aruba            4be15729-63ec-4e2a-83d7-63a9615da59b  wifi      wlp0s20f3
br-6066492249de  009488ec-ebd0-4990-8ddd-46cc2c079980  bridge    br-6066492249de
docker0          98af806f-defa-42c8-8dec-e811bd03a905  bridge    docker0
lo               773c166b-4785-49fb-8252-1dfa1e9c84f3  loopback  lo

This is the output of the conn command. Now, if you think your function needs more changes, you can go back to the function using funced conn command, edit the function, and test afterward until you get the desired result.

If you think the results are as expected, then you can use funcsave conn to save this function.

$ funcsave conn
funcsave: wrote /home/k7/.config/fish/functions/

This will write the function at the configuration directory of fish and now if you manage your dotfiles with git, you can version control this function and keep using on other devices too.

You can also see this function in the web-based view if you prefer to look at things in the browser.


Some Caveats of Using the Fish Shell

!! & !$

In bash, you can use !! to repeat the last command and !$ to get the last argument of the previous command. For someone transitioning from bash to fish, the absence of this feature in fish can be disorienting.

You can use the following function to make !! work in fish shell.

function last_history_item
    echo $history[1]
abbr -a !! --position anywhere --function last_history_item

If you want the last argument of the previous command then you can use Alt combined with upper arrow to achieve that.

If you want to add sudo to you current command then you can use Alt + S

Brace Expansion

In bash shell, we use $(command) and whatever is under this is replaced by the actual value of the command but in fish shell that is not supported. You must use the same thing without the $ sign.

$ sudo apt install linux-tools-$(uname -r)
fish: $(...) is not supported. In fish, please use '(uname)'.
sudo apt install linux-tools-$(uname -r)

Look at the unambiguous error message that fish shell gives you. It tells you that you can’t use $(...) in fish shell. You have to use '(...)' instead. Let’s try to run the same command with '(...)' instead of $(...).

$ sudo apt install linux-tools-(uname -r)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
The following NEW packages will be installed:
  linux-aws-tools-5.15.0-1028 linux-tools-5.15.0-1028-aws
0 upgraded, 2 newly installed, 0 to remove and 70 not upgraded.
Need to get 7645 kB of archives.
After this operation, 25.7 MB of additional disk space will be used.
Do you want to continue? [Y/n]

So, this is another thing you should remember while using fish shells.


Bash is not going anywhere. I’ve not seen a single instance where fish or any other shell except bash being used in CI-CD pipelines. So if you are working in a team where you have to work with bash scripts, you should be aware of the differences between bash and fish. I write scripts in bash. You can still write scripts that you’ll consume yourself in fish, but if you’re contributing to an open-source project or working with a team, chances are some of the team-members or community might not have used fish shell, and you should prefer bash over fish.


Exporting Variables

Above, we talked about exporting variables with -x, but fish also supports export syntax to be compatible with bash. To set you EDITOR variable to nvim, you can also use the bash syntax.

export EDITOR=nvim

from an implementation perspective, it used set under the hood to export variables.


This is yet another useful function that will help in updating your PATH for example, say you installed Go programming language on your system, and to add go bin directory to the path, you can use the following:

fish_add_path -U ~/go/bin


vared stands for variable edit. Let’s set a variable using set command. For example, set -x EDITOR code. Now let’s say you want to switch your EDITOR to nvim you can invoke the vared EDITOR command here. It’ll open up an interactive EDITOR where you can update the value.

$ vared EDITOR
> code

You can update the value from code to nvim



cdh is very handy for moving to directories you’ve recently visited. It’ll open up an interactive menu and ask you to choose the directory you want to enter.


You can then select the directory from the options and hit enter to go into that directory.


Fish is an outstanding shell if you work a lot in your terminal and care about productivity. If you do a lot of work in the command line, I’m sure that fish will help you be more productive. I’m not saying that this is the best shell out there, but it definitely has some great features.

Earthly Cloud: Consistent, Fast Builds, Any CI
Consistent, repeatable builds across all environments. Advanced caching for faster builds. Easy integration with any CI. 6,000 build minutes per month included.

Get Started Free

Anurag Kumar %
Anurag Kumar
Anurag is an engineering student who loves contributing to open source projects. He is primarily interested in cloud native ecosystem.



Get notified about new articles!
We won't send you spam. Unsubscribe at any time.