Zsh is quickly becoming the shell of choice for software developers and, if you want to maximise your productivity at the CLI, you should know how to create and autoload your own Zsh functions.
Even novice CLI users will have added the occasional alias in order to avoid repeatedly typing out a verbose command. Whilst aliases are great, they aren’t well suited to the more complicated, logical code snippets that you may want to run on a regular basis.
In this whistle-stop tour, I’ll show you how to create basic Zsh functions and integrate them with Zsh’s autoloading system so they’re available during interactive shell sessions.
Creating our function
After reading this article, I’m sure you’ll be inspired to write lots and lots of functions but, before you get carried away, it’s good practice to create a directory to store them all. I usually create a hidden subdirectory in my home directory called .zshfn
By convention, each function is stored in a file with the same name; for the purposes of keeping this guide snappy, I’m going to create a very simple clock function that just displays the current time and so I’ll create a file called clock (no file extension) to put my function code in.
mkdir ~/.zshfn
touch $_/clock
Now edit the clock file and add the following snippet:
# Initialisation Code
echo "Initialising Clock"
# Display simple clock every second
clock () {
while [ true ]
do
echo "=========="
date +"%r";
echo "=========="
sleep 1;
clear;
done
}
# Clear the three lines from the terminal
clear() {
for i in {1..3};
do
tput cuu1
tput el
done
}
clock
We’ll come back to the function later — for now just save the clock file.
Configuring our .zshrc file
In order to make our function accessible whenever we startup a new terminal, we need to make a couple of modifications to the .zshrc file.
First we need to ensure we add our .zshfn function directory to the Zsh file search path (a.k.a fpath) so that Zsh knows it should look in our new directory for functions. As you might expect, the fpath is traversed from left-to-right like other unix path variables.
Secondly, we need to tell Zsh about the clock function we just created — this is achieved via the autoload command. The autoload command essentially instructs Zsh to register a function (called clock) using the contents of the clock file that is located in a directory somewhere along the fpath.
# Bottom of .zshrc file
fpath=( ~/.zshfn "${fpath[@]}" )
autoload -Uz clock
As you add more functions, you’ll find it becomes cumbersome to append each one to the end of the autoload command and so (if you want to be super cool) you can choose to autoload all files in your .zshfn folder using the following alternative:
# Bottom of .zshrc file
fpath=( ~/.zshfn "${fpath[@]}" )
autoload -Uz $fpath[1]/*(.:t)
If you’re wondering what the -Uz options do, they simply ensure that autoloading will do the right thing regardless of what other options you may have configured (i.e. enforces disabled alias-expansion and zsh style autoloading).
Running our function
After saving our clock function and the .zshrc file we are now ready to test the function out. In your terminal begin typing clock — you should be able to tab-complete the function and if you have syntax highlighting turned on you should see that kick in too.
Hit return and you should see the following output:
Remember that as far as Zsh is concerned, before running the clock function, the entire contents of the clock file are used as the function definition. This is why the first time you run clock, you see the initialisation code being executed.
Kill the function (Ctrl-c) and run the function again — you should see the following output the second time around:
Notice that in the second execution, the initialisation code does not run— this is because the first time we ran the function, the clock function was redefined to use the actual clock() function defined in our file. This can be a really nice pattern to use if you have some sort of one-off initialisation you need to accomplish.
At this point you might be wondering why the clock() method is even needed? Well actually, it isn’t. You could replace the clock file with the following contents and it would work just fine.
echo "Initialising Clock"
while [ true ]
do
echo "=========="
date +"%r";
echo "=========="
sleep 1;
for i in {1..3};
do
tput cuu1
tput el
done
done
If you now execute this version of the clock function twice in a row you’ll see that, because the clock function is not redefined, the initialisation code runs both times.
Which approach you use is largely down to your use-case and stylistic preferences. I tend to prefer the first option (i.e. encapsulate my clock code in a clock() function block) but in many cases it is completely superfluous.
I hope you learned something from this quick write-up — there is a lot more to Zsh functions than covered here and if you want more detailed information, I’d recommend you check out this resource.
In any case, functions are a great way to optimise your CLI experience and it’s certainly worthwhile knowing how to use them 😎👍
Top comments (2)
is it possible to have one file with multiple functions and to add that to $fpath? I'd rather not have a separate file, such as 'clock', for every function...
Checkout wiki.zshell.dev if looking something advanced 🧙