Home » How to define and load your own shell function in zsh

How to define and load your own shell function in zsh


In zsh, the function search path ($fpath) defines a set of directories, which contain files that can be marked to be loaded automatically when the function they contain is needed for the first time.

Zsh has two modes of autoloading files: Zsh’s native way and another mode that resembles ksh’s autoloading. The latter is active if the KSH_AUTOLOAD option is set. Zsh’s native mode is the default and I will not discuss the other way here (see “man zshmisc” and “man zshoptions” for details about ksh-style autoloading).

Okay. Say you got a directory `~/.zfunc’ and you want it to be part of the function search path, you do this:

fpath=( ~/.zfunc "${fpath[@]}" )

That adds your private directory to the front of the search path. That is important if you want to override functions from zsh’s installation with your own (like, when you want to use an updated completion function such as `_git’ from zsh’s CVS repository with an older installed version of the shell).

It is also worth noting, that the directories from `$fpath’ are not searched recursively. If you want your private directory to be searched recursively, you will have to take care of that yourself, like this (the following snippet requires the `EXTENDED_GLOB’ option to be set):


It may look cryptic to the untrained eye, but it really just adds all directories below `~/.zfunc’ to `$fpath’, while ignoring directories called “CVS” (which is useful, if you’re planning to checkout a whole function tree from zsh’s CVS into your private search path).

Let’s assume you got a file `~/.zfunc/hello’ that contains the following line:

printf 'Hello world.n'

All you need to do now is mark the function to be automatically loaded upon its first reference:

autoload -Uz hello

“What is the -Uz about?”, you ask? Well, that’s just a set of options that will cause `autoload’ to do the right thing, no matter what options are being set otherwise. The `U’ disables alias expansion while the function is being loaded and the `z’ forces zsh-style autoloading even if `KSH_AUTOLOAD’ is set for whatever reason.

After that has been taken care of, you can use your new `hello’ function:

zsh% hello
Hello world.

A word about sourcing these files: That’s just wrong. If you’d source that `~/.zfunc/hello’ file, it would just print “Hello world.” once. Nothing more. No function will be defined. And besides, the idea is to only load the function’s code when it is required. After the `autoload’ call the function’s definition is not read. The function is just marked to be autoloaded later as needed.

And finally, a note about $FPATH and $fpath: Zsh maintains those as linked parameters. The lower case parameter is an array. The upper case version is a string scalar, that contains the entries from the linked array joined by colons in between the entries. This is done, because handling a list of scalars is way more natural using arrays, while also maintaining backwards compatibility for code that uses the scalar parameter. If you choose to use $FPATH (the scalar one), you need to be careful:


will work, while the following will not:


The reason is that tilde expansion is not performed within double quotes. This is likely the source of your problems. If echo $FPATH prints a tilde and not an expanded path then it will not work. To be safe, I’d use $HOME instead of a tilde like this:


That being said, I’d much rather use the array parameter like I did at the top of this explanation.

You also shouldn’t export the $FPATH parameter. It is only needed by the current shell process and not by any of its children.


Regarding the contents of files in `$fpath’:

With zsh-style autoloading, the content of a file is the body of the function it defines. Thus a file named “hello” containing a line echo "Hello world." completely defines a function called “hello”. You’re free to put
hello () { ... } around the code, but that would be superfluous.

The claim that one file may only contain one function is not entirely correct, though.

Especially if you look at some functions from the function based completion system (compsys) you’ll quickly realise that that is a misconception. You are free to define additional functions in a function file. You are also free to do any sort of initialisation, that you may need to do the first time the function is called. However, when you do you will always define a function that is named like the file in the file and call that function at the end of the file, so it gets run the first time the function is referenced.

If – with sub-functions – you didn’t define a function named like the file within the file, you’d end up with that function having function definitions in it (namely those of the sub-functions in the file). You would effectively be defining all your sub-functions every time you call the function that is named like the file. Normally, that is not what you want, so you’d re-define a function, that’s named like the file within the file.

I’ll include a short skeleton, that will give you an idea of how that works:

# Let's again assume that these are the contents of a file called "hello".

# You may run arbitrary code in here, that will run the first time the
# function is referenced. Commonly, that is initialisation code. For example
# the `_tmux' completion function does exactly that.
echo initialising...

# You may also define additional functions in here. Note, that these
# functions are visible in global scope, so it is paramount to take
# care when you're naming these so you do not shadow existing commands or
# redefine existing functions.
hello_helper_one () {
    printf 'Hello'

hello_helper_two () {
    printf 'world.'

# Now you should redefine the "hello" function (which currently contains
# all the code from the file) to something that covers its actual
# functionality. After that, the two helper functions along with the core
# function will be defined and visible in global scope.
hello () {
    printf '%s %sn' "$(hello_helper_one)" "$(hello_helper_two)"

# Finally run the redefined function with the same arguments as the current
# run. If this is left out, the functionality implemented by the newly
# defined "hello" function is not executed upon its first call. So:
hello "$@"

If you’d run this silly example, the first run would look like this:

zsh% hello
Hello world.

And consecutive calls will look like this:

zsh% hello
Hello World.

I hope this clears things up.

(One of the more complex real-world examples that uses all those tricks is the already mentioned `_tmux’ function from zsh’s function based completion system.)

The name of the file in a directory named by an fpath element must match the name of the autoloadable function it defines.

Your function is named my_function and ~/.my_zsh_functions is the intended directory in your fpath, so the definition of my_function should be in the file ~/.my_zsh_functions/my_function.

The plural in your proposed filename (functions_1) indicates that you were planning on putting multiple functions in the file. This is not how fpath and autoloading work. You should have one function definition per file.

Sourcing definitely isn’t the right approach, since what you seem to want is to have lazy initialized functions. That’s what autoload is for. Here’s how you accomplish what you’re after.

In your ~/.my_zsh_functions, you say you want to put a function called my_function that echos “hello world”. But, you wrap it in a function call, which isn’t how this works. Instead, you need to make a file called ~/.my_zsh_functions/my_function. In it, just put echo "Hello world", not in a function wrapper. You could also do something like this if you really do prefer to have the wrapper.

# ~/.my_zsh_functions/my_function
__my_function () {
    echo "Hello world";
# you have to call __my_function
# if this is how you choose to do it

Next, in your .zshrc file, add the following:

fpath=(~/.my_zsh_functions $fpath);
autoload -U ~/.my_zsh_functions/my_function

When you load a new ZSH shell, type which my_function. You should see this:

my_function () {
    # undefined
    builtin autoload -XU

ZSH just stubbed out my_function for you with autoload -X. Now, run my_function by just typing my_function. You should see Hello world print out, and now when you run which my_function you should see the function filled in like this:

my_function () {
    echo "Hello world"

Now, the real magic comes when you set up your whole ~/.my_zsh_functions folder to work with autoload. If you want every file you drop in this folder to work like this, change what you put in your .zshrc to something this:

# add ~/.my_zsh_functions to fpath, and then lazy autoload
# every file in there as a function
fpath=(~/.my_zsh_functions $fpath);
autoload -U $fpath[1]/*(.:t)

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...