[ 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 ]