Learn how to write fish shell scripts by example.

How to set variables

Here’s a simple example of assigning a value to a variable.

set MY_VAR "some value"

Here’s an example of appending values to a variable. By default fish variables are lists.

set MY_VAR $MY_VAR "another value"

This is how you can create lists.

set MY_LIST "value1" "value2" "value3"

Here’s an example of storing value returned from the execution of a command to a variable.

set OUR_VAR (math 1+2)
set OUR_VAR (date +%s)
set OUR_VAR (math $OUR_VAR / 60)

Since all fish variables are lists, you can access individual elements using [n] operator, where n=1 for the first element (not 0 index). Here’s an example. And negative numbers access elements from the end.

set LIST one two three
echo $LIST[1]  # one
echo $LIST[2]  # two
echo $LIST[3]  # three
echo $LIST[-1] # This is the same element as above

You can also use ranges from the variable / list, continuing the example above.

set LIST one two three
echo $LIST[1..2]  # one two
echo $LIST[2..3]  # two three
echo $LIST[-1..2] # three two

How to write for loops

Since variables contain lists by default, it is very easy to iterate thru them. Here’s an example.

set FOLDERS bin
set FOLDERS $FOLDERS .atom
set FOLDERS $FOLDERS github
for FOLDER in $FOLDERS
  echo "item: $FOLDER"
end

How to write if statements

The key to writing if statements is using the test command to evaluate some expression to a boolean. This can be string comparisons or even testing the existence of files and folders. Here are some examples.

String comparison in variable.

if test $hostname = "mymachine"
  echo "hostname is mymachine"
end

Checking for file existence.

if test -e "somefile"
  echo "somefile exists"
end

How to perform string comparisons

In order to test substring matches in strings you can use the string match command. Here is more information on the command:

  1. Official docs on string match.
  2. Stackoverflow answer on how to use it.

Here’s an example of this in action.

if string match -q "*myname*" $hostname
  echo "$hostname contains myname"
else
  echo "$hostname does not contain myname"
end

How to write switch statements for strings

In order to create switch statements for strings, the test command is used here as well (just like it was for if statements). The case statements need to match substrings, which can be expressed using a combination of wildcard chars and the substring you want to match. Here’s an example.

switch $hostname
case "*substring1*"
  echo "Matches $hostname containing substring1"
case "*substring2*"
  echo "Matches $hostname containing substring2"
end

You can combine this w/ if statements as well, and end up w/ something like this.

if test (uname) = "Darwin"
  echo "Machine is running macOS"
  switch $hostname
  case "*MacBook-Pro*"
    echo "hostname has MacBook-Pro in it"
  case "*MacBook-Air*"
    echo "hostname has MacBook-Air in it"
  end
else
  echo "Machine is not running macOS"
end

How to execute strings

The safest way to execute strings that are generated in the script is to use the following pattern.

echo "ls \
  -la" | sh

This not only makes it easier to debug, but also avoids strange errors when doing multi-line breaks using \.

How to write functions

A fish function is just a list of commands that may optionally take arguments. These arguments are just passed in as a list (since all variables in fish are lists).

Here’s an example.

function say_hi
  echo "Hi $argv"
end
say_hi
say_hi everbody!
say_hi you and you and you

Once you have written a function you can see what it is by using type, eg: type say_hi will show you the function that you just created above.

How to pass parameters to functions

Instead of using $argv to figure out what parameters were passed to a function, you can provide a list of named parameters that a function expects. Here is more information on this from the official docs.

Here’s an example.

function testFunction -a param1 param2
  echo "arg1 = $param1"
  echo "arg2 = $param2"
end
testFunction A B

How to use sed

This is useful for removing fragments of files that are not needed, especially when xargs is used to pipe the result of find.

Here’s an example that removes ./ from the start of each file that’s found.

echo "./.Android" | sed 's/.\///g'

Here’s a more complex example of using sed, find, and xargs together.

set folder .Android*
find ~ -maxdepth 1 -name $folder | sed 's/.\///g' | \
  xargs -I % echo "cleaned up name: %"

How to use xargs

This is useful for piping the output of some commands as arguments for more commands.

Here’s a simple example: ls | xargs echo "folders: ".

  • Which produces this: folders: idea-http-proxy-settings images tmp.
  • Note how the arguments are concatenated in the output.

Here’s a slightly different example using -I % which allows arguments to be placed anywhere (not just at the end).

ls | xargs -I % echo "folder: %"

Which produces this output:

folder: idea-http-proxy-settings
folder: images
folder: tmp

Note how the arguments are each in a separate line.

How to use cut to split strings

Let’s say you have a string "token1:token2" and you want to split the string and only keep the first part of it. This can be done using the following cut command.

echo "token1:token2" | cut -d ':' -f 1
  • -d ':' - this splits the string by the : delimiter
  • -f 1 - this keeps the first field in the tokenized string

Here’s a real example of finding all the HTML files in ~/github/developerlife.com with the string "fonts.googleapis" in it and then opening them up in subl.

cd ~/github/developerlife.com
echo \
"find . -name '*html' | \
 xargs grep fonts.googleapis | \
 cut -d ':' -f 1 | \
 xargs subl" \
 | sh

How to calculate how long the script took to run

set START_TS (date +%s)

# This is where your code would go.
sleep 5

set END_TS (date +%s)
set RUNTIME (math $END_TS - $START_TS)
set RUNTIME (math $RUNTIME / 60)
echo "⏲ Total runtime: $RUNTIME min ⏲"

Related Posts