.. _intro_zsh_shell_functions:

Shell Functions
===============

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

.. code-block:: text
   :caption: Define a simple function
   :name: shell_functions_define_yp

   % 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 :literal:`$1` expands to the first argument to :literal:`yp`. The function could have been equivalently defined in one of the following ways:

.. code-block:: text
   :caption: function keyword forms
   :name: shell_functions_function_keyword

   % 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:

.. code-block:: text
   :caption: Alias after function parse
   :name: shell_functions_alias_after_parse

   % 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:

.. code-block:: text
   :caption: Redefine with alias active
   :name: shell_functions_redefine_with_alias

   % 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:

.. code-block:: text
   :caption: Loop over multiple args
   :name: shell_functions_multiple_args

   % 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 :literal:`for i` loops through each of the function's arguments, setting :literal:`i` equal to each of them in turn. We can also make the function do something sensible if no arguments are given:

.. code-block:: text
   :caption: Check $# for usage
   :name: shell_functions_usage_check

   % 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

:literal:`$#` 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 :literal:`ypmatch` all of them.

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

.. code-block:: text
   :caption: Random line with RANDOM
   :name: shell_functions_randline

   % 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
   
   %

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

Function definitions can be viewed with the :literal:`functions` builtin:

.. code-block:: text
   :caption: View with functions builtin
   :name: shell_functions_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:

.. code-block:: text
   :caption: chmod +x as function
   :name: shell_functions_cx

   % 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:

.. code-block:: text
   :caption: Same trick as an alias
   :name: shell_functions_cx_as_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 :literal:`.zshrc`, all of which you may not use, it is often better to use the :literal:`autoload` builtin. The idea is, you create a directory where function definitions are stored, declare the names in your :literal:`.zshrc`, and tell the shell where to look for them. Whenever you reference a function, the shell will automatically load it into memory.

.. code-block:: text
   :caption: autoload with FPATH
   :name: shell_functions_autoload_fpath

   % 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 :literal:`#!` 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.)

.. code-block:: text
   :caption: Function as shell script
   :name: shell_functions_as_script

   % 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 :literal:`zsh`, or who don't want to copy all of your :literal:`.zshrc`, may use these functions as shell scripts.
