intro-4.html

[ prev | next | top ]

## 4. Shell Functions

zsh also allows you to create your own commands by defining shell functions. For example:

% yp () {
> ypmatch $1 passwd.byname
> }
% yp pfalstad
pfalstad:*:3564:35:Paul John Falstad:/u/pfalstad:/usr/princeton/bin/zsh

This function looks up a user in the NIS password map. The $1 expands to the first argument to yp. The function could have been equivalently defined in one of the following ways:

% function yp {
> ypmatch $1 passwd.byname
> }
% function yp () {
> ypmatch $1 passwd.byname
> }
% function yp () ypmatch $1 passwd.byname

Note that aliases are expanded when the function definition is parsed, not when the function is executed. For example:

% alias ypmatch=echo
% yp pfalstad
pfalstad:*:3564:35:Paul John Falstad:/u/pfalstad:/usr/princeton/bin/zsh

Since the alias was defined after the function was parsed, it has no effect on the function’s execution. However, if we define the function again with the alias in place:

% function yp () { ypmatch $1 passwd.byname }
% yp pfalstad
pfalstad passwd.byname

it is parsed with the new alias definition in place. Therefore, in general you must define aliases before functions.

We can make the function take multiple arguments:

% unalias ypmatch
% yp () {
> for i
> do ypmatch $i passwd.byname
> done
> }
% yp pfalstad subbarao sukthnkr
pfalstad:*:3564:35:Paul John Falstad:/u/pfalstad:/usr/princeton/bin/zsh
subbarao:*:3338:35:Kartik Subbarao:/u/subbarao:/usr/princeton/bin/zsh
sukthnkr:*:1267:35:Rahul Sukthankar:/u/sukthnkr:/usr/princeton/bin/tcsh

The for i loops through each of the function’s arguments, setting i equal to each of them in turn. We can also make the function do something sensible if no arguments are given:

% yp () {
> if (( $# == 0 ))
> then echo usage: yp name ...; fi
> for i; do ypmatch $i passwd.byname; done
> }
% yp
usage: yp name ...
% yp pfalstad sukthnkr
pfalstad:*:3564:35:Paul John Falstad:/u/pfalstad:/usr/princeton/bin/zsh
sukthnkr:*:1267:35:Rahul Sukthankar:/u/sukthnkr:/usr/princeton/bin/tcsh

$# is the number of arguments supplied to the function. If it is equal to zero, we print a usage message; otherwise, we loop through the arguments, and ypmatch all of them.

Here’s a function that selects a random line from a file:

% randline () {
> integer z=$(wc -l <$1)
> sed -n $[RANDOM % z + 1]p $1
> }
% randline /etc/motd
PHOENIX WILL BE DOWN briefly Friday morning, 5/24/91 from 8 AM to
% randline /etc/motd
SunOS Release 4.1.1 (PHOENIX) #19: Tue May 14 19:03:15 EDT 1991
% randline /etc/motd
| Please use the "msgs" command to read announcements. Refer to the |
% echo $z

%

randline has a local variable, z, that holds the number of lines in the file. $[RANDOM % z + 1] expands to a random number between 1 and z. An expression of the form $[...] expands to the value of the arithmetic expression within the brackets, and the RANDOM variable returns a random number each time it is referenced. % is the modulus operator, as in C. Therefore, sed -n $[RANDOM%z+1]p picks a random line from its input, from 1 to z.

Function definitions can be viewed with the functions builtin:

% functions randline
randline () {
integer z=$(wc -l <$1)
sed -n $[RANDOM % z + 1]p $1

}
% functions
yp () {
if let $# == 0
then
echo usage: yp name ...
fi
for i
do
ypmatch $i passwd.byname
done

}
randline () {
integer z=$(wc -l <$1)
sed -n $[RANDOM % z + 1]p $1

}

Here’s another one:

% cx () { chmod +x $* }
% ls -l foo bar
-rw-r--r-- 1 pfalstad 29 May 24 04:38 bar
-rw-r--r-- 1 pfalstad 29 May 24 04:38 foo
% cx foo bar
% ls -l foo bar
-rwxr-xr-x 1 pfalstad 29 May 24 04:38 bar
-rwxr-xr-x 1 pfalstad 29 May 24 04:38 foo

Note that this could also have been implemented as an alias:

% chmod 644 foo bar
% alias cx=’chmod +x’
% cx foo bar
% ls -l foo bar
-rwxr-xr-x 1 pfalstad 29 May 24 04:38 bar
-rwxr-xr-x 1 pfalstad 29 May 24 04:38 foo

Instead of defining a lot of functions in your .zshrc, all of which you may not use, it is often better to use the autoload builtin. The idea is, you create a directory where function definitions are stored, declare the names in your .zshrc, and tell the shell where to look for them. Whenever you reference a function, the shell will automatically load it into memory.

% mkdir /tmp/funs
% cat >/tmp/funs/yp
ypmatch $1 passwd.byname
ˆD
% cat >/tmp/funs/cx
chmod +x $*
ˆD
% FPATH=/tmp/funs
% autoload cx yp
% functions cx yp
undefined cx ()
undefined yp ()
% chmod 755 /tmp/funs/{cx,yp}
% yp egsirer
egsirer:*:3214:35:Emin Gun Sirer:/u/egsirer:/bin/sh
% functions yp
yp () {
ypmatch $1 passwd.byname
}

This idea has other benefits. By adding a #! header to the files, you can make them double as shell scripts. (Although it is faster to use them as functions, since a separate process is not created.)

% ed /tmp/funs/yp
25
i
#! /usr/local/bin/zsh
w
42
q
% </tmp/funs/yp
#! /usr/local/bin/zsh
ypmatch $1 passwd.byname
% /tmp/funs/yp sukthnkr
sukthnkr:*:1267:35:Rahul Sukthankar:/u/sukthnkr:/usr/princeton/bin/tcsh

Now other people, who may not use zsh, or who don’t want to copy all of your .zshrc, may use these functions as shell scripts.

[ prev | next | top ]