As soon as I started trying to address shortcomings with the note function from Part One, I discovered a problem: I don’t have the patience to edit functions stored in ~/.zshrc.

So, this tutorial is about loading functions from a personal directory. I’ll return to the note function in a future post.

If you’re just looking an example of configuring zsh to load a directory of functions, jump to the end. If you want to understand how the process works, continue reading.

Loading functions from a directory

The method zsh provides for loading functions from a directory is simple. All you need to do is create a directory, add it to $fpath, and use autoload to load your functions.

I’m using ~/functions as the directory for this demonstration, but you can do this in any location.

mkdir ~/functions
cd ~/functions

Navigate to your functions directory and, to prevent effecting the current shell, start a new shell. Then autoload a function.

autoload fn

The function doesn’t exist yet, but that’s not a problem.

When the function is called, the shell will check each directory listed in the special parameter $fpath, from left to right, looking for a file with the same name as the function.

% print -l $fpath

To create a function definition the shell can find, first we need to add our directory to the $fpath array:

fpath=(~/functions $fpath)

Next, create a file named “fn” containing the body of the function. Conveniently, the surrounding functionname () { ... } is not required for functions loaded this way:

cat << EOF > fn
# example function
print i am the function called fn

See Heredoc if you’re not familiar with the above syntax for creating a file.

When function is called, the shell finds the definition, then loads and runs it.

% fn
i am the function called fn

After it’s been loaded, changes to the function’s definition will have no effect on the current shell. To refresh a function’s definition, use unfunction before loading it again.

% cat << EOF > fn
> print i am fn
% fn
i am the function called fn
% unfunction fn
% autoload fn
% fn
i am fn

autoload -Uz

Although it’s not required for our function, the common form for using autoload is:

autoload -Uz fn

The -U option suppresses alias expansion when the function is loaded, which is recommended for the use of functions supplied with the zsh distribution. The -z option forces zsh-style loading.

To get into the habit, further examples will follow this form.

Loading multiple functions

The autoload command can accept multiple arguments. To demonstrate this, first create a second function in our directory:

cat << EOF > fn2
print i am fn2

Then autoload both functions:

% autoload -Uz fn fn2
% fn
i am fn
% fn2
i am fn2

You can load an entire directory by using globbing to supply the names of all the functions:

autoload -Uz ${fpath[1]}/*(:t)

The ${fpath[1]}/* expands to all the files in the first directory listed in $fpath. The (:t) is a globbing modifier which takes the tail (basename) of all the files in the list.

See Modifiers in the zshexpn manual pages for more information.

Configuring zsh to load functions

To add a directory to $fpath and autoload functions individually, I can add this to ~/.zshrc:

typeset -U fpath
fpath=(~/functions $fpath)
autoload -Uz fn fn2  # load example functions

The typeset -U fpath command forces the shell to keep only the first occurrence of each duplicated value in the parameter fpath.

I prefer to autoload the entire directory. To avoid loading the functions again if ~/.zshrc is sourced again, I first perform a test to determine whether or not the directory is already in $fpath:

typeset -U fpath
if [[ -z ${fpath[(r)$my_functions]} ]] ; then
    fpath=($my_functions $fpath)
    autoload -Uz ${my_functions}/*(:t)

The (r) subscript flag above returns an empty string if the expression that follows it (my_functions) doesn’t match any element in the array. The ‘-z’ test flag returns true if the string is empty.

See Subscript Flags in the zshparam manual pages for more information.

export fpath?

I’ve found no suggestion in the zsh manual pages or User’s Guide that the $fpath parameter should be exported. I’m not aware of any reason it should be. So, contrary fairly common advice, I’ve chosen not to do so. I’ll update this post if I come to regret the decision.

Next steps

Now that I can save and edit functions in separate files, I’m ready to start improving the note function from Part One, which will be the topic of the next post in this series.

As always, please share any related thoughts, corrections, or questions on Twitter.