home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Unix Power ToolsUnix Power ToolsSearch this book

29.13. Propagating Shell Functions

One easy way to define shell functions that you'll have every time you start a new shell is by defining them in your shell setup files (Section 3.3). Here are two other ways.

29.13.1. Exporting bash Functions

In bash , you can export functions to other bash subshells (Section 24.4). (The original Korn shell, but not the public-domain version, supposedly does this too, but I haven't had much luck with it.) Just use the command typeset -fx funcname, where funcname is the name of the function.

How does this work? It stores the function in an environment variable (Section 35.3) whose value starts with ( ). You can see this with printenv or env (Section 35.3). For example, let's define a simple function named dir, export it, start a subshell, run the function, and look for it in the environment:

bash$ function dir( ) { ls -F "$@"; }
bash$ typeset -fx dir         ...export the function
bash$ bash                    ...start subshell
bash$ dir                     ...the function still works
,ptbk.last            ch14.sgm    ch36.ps.gz          fmt/
,xrefs.list           ch15.ps.gz  ch36.sgm            gmatlogs/
bash$ printenv
   ...lots of environment variables...
dir=( ) {  ls -F "$@"
}

29.13.2. FPATH Search Path

Both ksh and zsh will automatically search for functions in the PATH variable (Section 35.6). So you can put a function in a file with the same name as the function (for instance, put the function foo in a file named foo), and make the file executable (with chmod +x foo (Section 35.1)), and then the shell can find the function.

I don't like to use PATH for function-searching, though. One reason is that PATH is passed to all Unix processes -- but if the process isn't a shell and it tries to execute a function file, it'll probably fail in an ugly way.[94] Also, making a file executable if you don't tell the kernel how to execute it seems to me a recipe for trouble. A better way to help the shell find functions is to set a function search path in the FPATH environment variable; it has the same syntax as PATH. (In zsh, you can also set the fpath array -- with the same syntax as path.) In FPATH, list directories that hold function files. In ksh, those files don't even need execute permission! Then ksh and zsh will search the FPATH directories if they can't find an executable file in the PATH.

[94]zsh lets you define a function in a function file without the enclosing funcname( ) { and } syntax. Then the file could be directly executed in a subshell by some shell that doesn't understand functions. I'm not sure I'd ever use this because running a function this way -- as an external command instead of an internal command (Section 1.9) -- means the function can't access or modify the environment of the shell that's running it, which is one of the reasons for writing a shell function in the first place! But, like everything in zsh, I'm sure someone had a good reason for making this work.

Would you like the shells to search FPATH before PATH, so that a function will be executed before a standard command with the same name? (I would. After all, if I define a function from a shell prompt or shell setup file like .zshrc, that function will be run instead of a standard executable.) Here's how to set that up. Tell the shell to autoload the function. Autoloading happens automatically if there's no match found in PATH -- because, as I said above, the shell falls back to FPATH if it doesn't find a match in PATH. But if you want the shell to look for a particular name in FPATH before it tries PATH, you have to autoload the function. Autoloading a function doesn't actually define the function (read the function body into the shell); it simply declares that the function exists -- so the shell will remember that when you eventually want to execute the function.

This has a few twists, so let's look at each shell separately. You might want to do this yourself and follow along: When I first played with FPATH, I made two subdirectories of /tmp named a and b. Each directory had three simple function files named func1, func2, and foo. The functions func1 and func2 simply echo a message with their name and location. foo invokes a shell script of the same name, but first uses set -xv (Section 37.1) for debugging. func1 was a single-line function and func2 was multiline. The files in /tmp/a weren't executable, and the ones in /tmp/b were executable. I set the FPATH environment variable (set the shell variable and exported it) to /tmp/a:/tmp/b -- so the shells should try the nonexecutable function files before falling back to the executables. After setting that up, I started a ksh subshell and played around. Then I exited the ksh and started a zsh.

29.13.2.1. Korn shell

Here's what happened in pdksh. The standard ksh is similar but not as verbose:

$ echo $FPATH
/tmp/a:/tmp/b
$ type func1
func1 is a undefined (autoload from /tmp/a/func1) function
$ func1
This is func1 from /tmp/a, a single-line unexecutable function
$ type func1
func1 is a function

$ typeset -f func2
$ type func2
func2 is a undefined (autoload from /tmp/a/func2) function
$ func2
This is func2 from /tmp/a, a multi-line unexecutable function
$ typeset -f func2
func2( ) {
    echo "This is func2 from /tmp/a, a multi-line unexecutable function"
}

$ type foo
foo is /home/jpeek/.bin/foo
$ autoload foo
$ type foo
foo is a undefined (autoload from /tmp/a/foo) function
$ cat /tmp/a/foo
foo( ) { sh -xv $HOME/.bin/foo "$@"; }
$ foo
#!/bin/sh
echo "Welcome to the exciting $0 program..."
+ echo Welcome to the exciting /home/jpeek/.bin/foo program...
Welcome to the exciting /home/jpeek/.bin/foo program...
$ type foo
foo is a function

Here's what happened with func1, func2, and foo:

  • First, without autoloading, I use type (Section 2.6) to see if the shell has found func1 anywhere. There's no func1 along the PATH, so the shell searches FPATH -- and finds it. So func1 is automatically marked for autoloading; note that I didn't have to autoload it myself because there's no func1 in a PATH directory. I run func1, then use type again; now the shell confirms that it's read the function definition and func has been loaded into the shell.

  • Next I played with func2. typeset -f (Section 29.11) shows that the shell doesn't have a definition for the function yet, but type shows that the function declaration has been autoloaded. (This isn't just academic. If you edit a function definition file, it's good to know whether the shell has already loaded a copy of a previous definition.) I run the function, then use typeset to display the function, which has been loaded (of course!) by now.

  • Because there's a program named foo in my PATH, type shows that. But I want the shell to use my front-end foo function, so I run autoload -- and then type confirms that the shell looked down FPATH and found the function in /tmp/a. The function definition hasn't been loaded yet, so I use cat (Section 12.2) to display the function file. I run the foo function; because it set the shell's verbose and echo flags, you can see the contents of the foo shell script and the commands that are executed. Finally, type shows that the shell will now run the function when I execute foo.

If you'd like to be sure that all the functions in your FPATH are autoloaded -- especially if you add new ones pretty often -- here's a way to do it. Put code like this in your ENV setup file (Section 3.3):

IFS Section 36.23, for Section 28.9

# Autoload all functions in FPATH directories.
# Temporarily add a colon (:) to IFS to parse FPATH:
old_ifs="$IFS"; IFS=":$IFS"
for d in $FPATH
do autoload `ls $d`
done
IFS="$oldifs"; unset old_ifs

If a directory in FPATH is empty, autoload gets no arguments and, in that case, shows the function definitions it has already autoloaded. I only put a directory in my FPATH if it has functions to load. If you might have an empty directory in yours, you can avoid seeing the autoload output by editing that code to store the output of ls in a shell variable and running autoload only if the variable isn't empty.

29.13.2.2. zsh

The zsh system is mostly like ksh. The difference is that zsh doesn't automatically search FPATH. You have to manually autoload any function that you want zsh to search for in FPATH.

zsh$ echo $FPATH
/tmp/a:/tmp/b
zsh$ type func1
func1 not found
zsh$ func1
zsh: command not found: func1
zsh$ autoload func1
zsh$ type func1
func1 is a shell function
zsh$ func1
This is func1 from /tmp/a, a single-line unexecutable function
zsh$ type func1
func1 is a shell function

zsh$ autoload func2
zsh$ typeset -f func2
undefined func2 ( ) { }
zsh$ func2
This is func2 from /tmp/a, a multi-line unexecutable function
zsh$ typeset -f func2
func2 ( ) {
   echo "This is func2 from /tmp/a, a multi-line unexecutable function"
}

zsh$ type foo
foo is /home/jpeek/.bin/foo
zsh$ autoload foo
zsh$ foo
#!/bin/sh
echo "Welcome to the exciting $0 program..."
+ echo Welcome to the exciting /home/jpeek/.bin/foo program...
Welcome to the exciting /home/jpeek/.bin/foo program...
zsh$ type foo
foo is a shell function

I won't repeat all of the explanation from the ksh section. Instead, let's just look at the differences:

  • The first examples show that zsh won't look down FPATH for func1. Once you autoload the function, type doesn't give you a clue whether the function has been defined or just declared.

  • In zsh, you can see whether a function has been defined by using typeset -f (instead of type). After autoloading it, func2 has been declared but not defined. As the example shows, running the function once loads the definition.

If you'd like to be sure that all the functions in your FPATH are autoloaded -- especially if you add new ones pretty often -- here's how to do it in zsh. Put code like this in a per-shell setup file (Section 3.3) -- typically .zshrc:

# Autoload all functions in fpath directories:
for d in $fpath
do autoload `ls $d`
done

The code is simpler than in ksh because we can step through the fpath array without parsing it at colon (:) characters. As in ksh, though, you'll want to tweak the code if a directory in fpath might be empty: store the output of ls in an array and run autoload only if the array has members.

-- JP



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.