New features in the fish shell

Fish (the “friendly interactive shell”) has the explicit goal of being more user-friendly than other shells. It features a modern command-line interface with syntax highlighting, tab completion, and auto-suggestions out of the box (all with no configuration required). Unlike many of its competitors, it doesn’t care about being POSIX-compliant but attempts to blaze its own path.

Since our last look at the project, way back in 2013, it has seen lots of new releases with features, bug fixes, and refinements aimed at appealing to a wide range of users. Some of the biggest additions landed in the 3.0 release, but we will also describe some other notable changes from version 2.1 up through latest version.

Fish Shell

The first release of fish was made by Axel Liljencrantz in February 2005. Version 2.0 was released in September 2013, and 3.0 in December 2018. The latest version, 3.1.2, was released on April 29, 2020.

Logical operators

Prior to the 3.0 release, fish lacked a special syntax to express logical AND (&&) and OR (||) operations. Instead, the and and or commands were used for such operations. These commands verify the previous command’s exit status before acting accordingly. For example, command1; and command2 expresses a logical AND operation in which command2 is executed if, and only if, command1 returns an exit status of zero.

This syntax proved to be unpopular among converts from other shells where operators such as &&, ||, and ! were used to express logical operations and it was the subject of several discussions on the project’s issue tracker. Due to popular demand, these logical operators are now supported as of fish 3.0 to make it easier to migrate from other shells. It is now possible to enter command1 && command2 in the shell or in a script to express the logical AND operation.

History and incognito mode

When a command is executed in the shell, it is inserted at the end of the history file located at ~/.local/share/fish/fish_history, accompanied by the timestamp of its entry. It is also possible to save a command to the history without executing it using the Alt+# binding (introduced in version 2.3.0). It toggles the current command line between commented and uncommented states. As with Bash and other shells, the up or down arrow keys can be used to search forward or backward in the history.

One nice feature for fish history is the ability to filter through the history based on what is typed into the shell. Entering a few characters and then hitting the up arrow key performs a history search for the commands that include the typed characters. For example, typing git and hitting the up arrow will display only the commands that contain the string git.

Fish does not enter any command that begins with a space into its history file, essentially treating it like an incognito command. This often surprises users who are transitioning to fish from Bash or other shells that do not have this behavior; it is also easy to activate it unintentionally when pasting commands to the terminal from other sources. There have been several discussions in GitHub issues on whether to retain this behavior in future releases and how to make it more discoverable. As of fish 3.1.2, no changes have been made to this aspect of the shell behavior.

A new addition in fish 3.0 is the --private flag that can be used to start fish in private mode; it stops all subsequent commands from being logged in the history file. This is handy for when a user wants to enter sensitive information in a command. The $fish_private_mode variable was also added for the purpose of detecting if a script is being run in private mode so that its behavior may be modified accordingly to respect the user’s wish for privacy.

Aliases and abbreviations

Like many other shells, fish provides the ability to define memorable alternate names (known as aliases) for commonly used commands. Aliases can help to eliminate a great deal of typing when working in the command line. There are two ways to create aliases in fish. The first involves creating a function wrapping a command:

config.fish
function gc
  git commit $argv
end

The second option is to use the alias keyword, which is essentially a shell wrapper for the function syntax above. When using this syntax, the $argv arguments string will be appended automatically.

config.fish
alias gc="git commit"

The release of fish 2.2 brought support for abbreviations to the shell. These are similar to standard aliases, but expand inline to the full command when typed. For example, a command can be shortened to yul using the following:

config.fish
abbr yul "yarn upgrade --latest"

Once yul is entered into the terminal followed by the space or enter key, it will be expanded into the full command. By default, abbreviations are stored to the universal scope, so they immediately become available in all current fish sessions and subsequent ones as well. To make an abbreviation visible in the current session only, use the (surprisingly named) --global flag when creating it. This places the abbreviation in the global scope which is local to the current shell. Abbreviations are generally better than aliases because the full command can be seen before executing it, which makes it easier to edit for a one-off change. They are also more suitable for interactive demos or presentations because the instructor can use a shortcut without obscuring the full command.

Vi mode

The default key bindings used by fish for moving the cursor around on the command line are from the Emacs editor. Examples are Ctrl+A to move the cursor to the start of a command, Ctrl+E to move to the end of a command, and so on. Fish 2.2 introduced a Vi mode for those who prefer bindings from the Vi editor. It supports command mode, insert mode, and visual mode bindings. Fish also provides shared bindings that are accessible regardless of your preferred mode.

If neither mode is sufficient for a particular edit, an external editor may be summoned using the Alt+E or Alt+V shortcuts. The two commands are synonymous; the editor is chosen from the first available of the $VISUAL or $EDITOR environmental variables.

Clipboard integration

Fish features an Emacs-style kill ring for blocks of text that were previously killed. For example, Ctrl+U cuts from the cursor position to the start of the line and inserts the text into the kill ring while Ctrl+Y pastes the latest value from the kill ring at the cursor position.

A change in Fish 2.4 is that any text cut with kill ring commands no longer overrides the system clipboard. This behavior was changed because it was a common source of frustration when pasting to the shell from external sources because cutting some text would override the previously copied text meaning that it would have to be copied again. Working with the system clipboard is still supported on Linux (X11 and Wayland) and macOS. Ctrl+X is used to copy the whole line to the clipboard (regardless of cursor position) and Ctrl+V pastes from the clipboard to the shell.

Web configuration tool

The web configuration tool, launched with fish_config, provides a nice way to customize the prompt style, color theme, and so on. An important security enhancement landed in 2.1.1 which prevents a remote-code-execution attack when running this web interface by using an authentication token to protect requests and responding only to requests that include this token.

In addition, the behavior of this tool was changed slightly in version 3.1 by causing it to display a list of the commands it is executing, for debugging purposes. Other improvements include new theme presets, base-16 color options, prompt styles, and support for viewing, editing, and adding abbreviations.

Fish web configuration tool

Fish web configuration tool

Wrap it up

Fish was originally implemented in C but is now primarily written in C++. It does not follow any particular release cycle but major versions usually take a few years to come out. The best way to follow the development of the shell is through its GitHub repository and official mailing list. The changes that are being planned for upcoming releases are detailed on the milestones page on GitHub. An official Gitter channel also exists for community discussions.

Overall, the refinements being made continue the process of making fish easier and more convenient to use compared to other shells. It strikes a good balance between having useful defaults to get started with immediately and leaving room for extensibility and customizability. A tutorial is available for those looking to quickly get up to speed with the precise differences between fish and more traditional shells.