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):
fpath=(
~/.zfuncs
~/.zfuncs/**/*~*/(CVS)#(/N)
"${fpath[@]}"
)
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:
FPATH=~/.zfunc:$FPATH
will work, while the following will not:
FPATH="~/.zfunc:$FPATH"
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:
FPATH="$HOME/.zfunc:$FPATH"
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.
Update
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 %s\n' "$(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
initialising...
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.)
Environment variables containing functions are a bash hack. Zsh doesn't have anything similar. You can do something similar with a few lines of code. Environment variables contain strings; older versions of bash, before Shellshock was discovered, stored the function's code in a variable whose name is that of the function and whose value is () {
followed by the function's code followed by }
. You can use the following code to import variables with this encoding, and attempt to run them with bash-like settings. Note that zsh cannot emulate all bash features, all you can do is get a bit closer (e.g. to make $foo
split the value and expand wildcards, and make arrays 0-based).
bash_function_preamble='
emulate -LR ksh
'
for name in ${(k)parameters}; do
[[ "-$parameters[name]-" = *-export-* ]] || continue
[[ ${(P)name} = '() {'*'}' ]] || continue
((! $+builtins[$name])) || continue
functions[$name]=$bash_function_preamble${${${(P)name}#"() {"}%"}"}
done
(As Stéphane Chazelas, the original discoverer of Shellshock, noted, an earlier version of this answer could execute arbitrary code at this point if the function definition was malformed. This version doesn't, but of course as soon as you execute any command, it could be a function imported from the environment.)
Post-Shellshock versions of bash encode functions in the environment using invalid variable names (e.g. BASH_FUNC_myfunc%%
). This makes them harder to parse reliably as zsh doesn't provide an interface to extract such variable names from the environment.
I don't recommend doing this. Relying on exported functions in scripts is a bad idea: it creates an invisible dependency in your script. If you ever run your script in an environment that doesn't have your function (on another machine, in a cron job, after changing your shell initialization files, …), your script won't work anymore. Instead, store all your functions in one or more separate files (something like ~/lib/shell/foo.sh
) and start your scripts by importing the functions that it uses (. ~/lib/shell/foo.sh
). This way, if you modify foo.sh
, you can easily search which scripts are relying on it. If you copy a script, you can easily find out which auxiliary files it needs.
Zsh (and ksh before it) makes this more convenient by providing a way to automatically load functions in scripts where they are used. The constraint is that you can only put one function per file. Declare the function as autoloaded, and put the function definition in a file whose name is the name of the function. Put this file in a directory listed in $fpath
(which you may configure through the FPATH
environment variable). In your script, declare autoloaded functions with autoload -U foo
.
Furthermore zsh can compile scripts, to save parsing time. Call zcompile
to compile a script. This creates a file with the .zwc
extension. If this file is present then autoload
will load the compiled file instead of the source code. You can use the zrecompile
function to (re)compile all the function definitions in a directory.
Best Answer
In zsh 5.3 or above,
should return something like
With
zsh
5.4 or above, you can also use:When you run
zsh
with thextrace
option (like withzsh -x
), it writes debugging information on stderr that shows every command it runs (not function definitions though). You can modify the$PS4
variable (the prompt variable used for thextrace
output, seeinfo zsh PS4
) so it gives you more information like for each command that it runs, from which file and on each line the command was read from.Would run a new zsh interactive shell instance, with stderr filtered by grep to show the lines that contain
precmd_func
.Or with
zsh
, you can invoke that_precmd_function_domore
function underxtrace
and with%x:%I
in$PS4
to see where the function definition was read from:(note the off-by-two line number here though).