29.13. Propagating Shell FunctionsOne 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 FunctionsIn 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 PathBoth 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.
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 shellHere'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:
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. zshThe 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:
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 Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|