Fish shell scripting guide

16 Mar 2020

The user-friendly fish shell doesn’t have too many guides for writing scripts from scratch. Learn the basics of writing a fish shell script here!

basic fish code

History

The fish shell has sound design principles for shells in general, opting towards the Apple model of keepings things simple and avoiding too much configurability. bash and zsh are highly configurable and contain some overlapping language features, at the expense of ease of learning and discoverability.

Features

Some of the shell’s absolutely killer features include:

If this is at all interesting, feel free to install it

Motivation

Unfortunately, the fish shell community is not as extensive as bash’s and zsh’s, so resources for writing scripts are lacking. This post aims to clear that up and provide a comprehensive set of mini recipes useful while writing scripts in the fish shell language.

The fish shell is meant to restrict features to stay “orthogonal”, so it is not fully POSIX-compliant. The more esoteric or strange POSIX tendencies have been removed in favor of simplicity. For example, there is no $" variable. Since the fish shell syntax is meant to be legible, we should be writing more scripts for it!

Resources

The following resources provide some great information about the fish shell and a few programming idioms, some of which are also covered in this post.

Starting

For the filename, fish scripts typically end with in .fish, e.g. test.fish.

Just like any other shell script, you have to declare which shell you will use.

#!/usr/bin/env fish

Variables

Although the fish shell recently introduced the = syntax to set variables for just one command, e.g. myvar=something echo $myvar, the set command will do most of the work in the script. Here are some common interactions with variables:

Conditionals

Conditions are mostly tested using the test function.

Loops

Functions

Functions are easy to define and use in fish shell scripts, and you are strongly encouraged to use them as much as possible. The documentation string makes it even clearer to split things up. Remember that $argv is sacred and used for all functions and also at the top-level.

Example definition and use:

function func_name -d "Function description string"
  set arg1 $argv[1]
  set arg2 $argv[2]
  echo $arg1
  echo $arg2
end
func_name first_arg second_arg

Fish directories

The fish shell has a few special areas that it create designate on installation, usually in the user’s $HOME.

Argument parsing

There is a fish package for option parsing available called fish-getopts. Since the fish shell prides itself on just working out of the box with minimal extra packages or configuration, we will create something similar from scratch using everything from the previous sections.

#!/usr/bin/env fish
function user_script -d "Does the main work of the script"
  set long 0
  set message ''
  while set -q argv[1]
    set option $argv[1]
    switch "$option"
      case -l --long
        set long 1
      case -m --message
        set -e argv[1]
        set message $argv[1]
      case "*"
        echo "Done processing flags"
        break
    end
    set -e argv[1]
  end
  for arg in $argv
    if test $long = '1'
      printf "%s: Printing a long message for arg %s\n" "$message" "$arg"
    else
      printf "%s: arg %s\n" "$message" "$arg"
    end
  end
end

function usage -d "Show all usage examples"
  echo "Usage: user_script.fish [-l] [-m message] [args]"
  exit 1
end

if test (count $argv) -gt 0
  user_script $argv
else
  usage
end

A full example: pomo.fish

All of these techniques were used in the development in a simple pomodoro timer implementation, found at pomo.fish.

If you have any questions or comments, feel free to reach out.

Enjoy the fish shell!