33.4. Recursion

Can a script recursively call itself? Indeed.


Example 33-8. A (useless) script that recursively calls itself

   1 #!/bin/bash
   2 # recurse.sh
   3 
   4 #  Can a script recursively call itself?
   5 #  Yes, but is this of any practical use?
   6 #  (See the following.)
   7 
   8 RANGE=10
   9 MAXVAL=9
  10 
  11 i=$RANDOM
  12 let "i %= $RANGE"  # Generate a random number between 0 and $RANGE - 1.
  13 
  14 if [ "$i" -lt "$MAXVAL" ]
  15 then
  16   echo "i = $i"
  17   ./$0             #  Script recursively spawns a new instance of itself.
  18 fi                 #  Each child script does the same, until
  19                    #+ a generated $i equals $MAXVAL.
  20 
  21 #  Using a "while" loop instead of an "if/then" test causes problems.
  22 #  Explain why.
  23 
  24 exit 0
  25 
  26 # Note:
  27 # ----
  28 # This script must have execute permission for it to work properly.
  29 # This is the case even if it is invoked by an "sh" command.
  30 # Explain why.


Example 33-9. A (useful) script that recursively calls itself

   1 #!/bin/bash
   2 # pb.sh: phone book
   3 
   4 # Written by Rick Boivie, and used with permission.
   5 # Modifications by ABS Guide author.
   6 
   7 MINARGS=1     #  Script needs at least one argument.
   8 DATAFILE=./phonebook
   9               #  A data file in current working directory
  10               #+ named "phonebook" must exist.
  11 PROGNAME=$0
  12 E_NOARGS=70   #  No arguments error.
  13 
  14 if [ $# -lt $MINARGS ]; then
  15       echo "Usage: "$PROGNAME" data-to-look-up"
  16       exit $E_NOARGS
  17 fi      
  18 
  19 
  20 if [ $# -eq $MINARGS ]; then
  21       grep $1 "$DATAFILE"
  22       # 'grep' prints an error message if $DATAFILE not present.
  23 else
  24       ( shift; "$PROGNAME" $* ) | grep $1
  25       # Script recursively calls itself.
  26 fi
  27 
  28 exit 0        #  Script exits here.
  29               #  Therefore, it's o.k. to put
  30               #+ un-hashmarked comments and data after this point.
  31 
  32 # ------------------------------------------------------------------------
  33 Sample "phonebook" datafile:
  34 
  35 John Doe        1555 Main St., Baltimore, MD 21228          (410) 222-3333
  36 Mary Moe        9899 Jones Blvd., Warren, NH 03787          (603) 898-3232
  37 Richard Roe     856 E. 7th St., New York, NY 10009          (212) 333-4567
  38 Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678
  39 Zoe Zenobia     4481 N. Baker St., San Francisco, SF 94338  (415) 501-1631
  40 # ------------------------------------------------------------------------
  41 
  42 $bash pb.sh Roe
  43 Richard Roe     856 E. 7th St., New York, NY 10009          (212) 333-4567
  44 Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678
  45 
  46 $bash pb.sh Roe Sam
  47 Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678
  48 
  49 #  When more than one argument is passed to this script,
  50 #+ it prints *only* the line(s) containing all the arguments.


Example 33-10. Another (useful) script that recursively calls itself

   1 #!/bin/bash
   2 # usrmnt.sh, written by Anthony Richardson
   3 # Used with permission.
   4 
   5 # usage:       usrmnt.sh
   6 # description: mount device, invoking user must be listed in the
   7 #              MNTUSERS group in the /etc/sudoers file.
   8 
   9 # ----------------------------------------------------------
  10 #  This is a usermount script that reruns itself using sudo.
  11 #  A user with the proper permissions only has to type
  12 
  13 #   usermount /dev/fd0 /mnt/floppy
  14 
  15 # instead of
  16 
  17 #   sudo usermount /dev/fd0 /mnt/floppy
  18 
  19 #  I use this same technique for all of my
  20 #+ sudo scripts, because I find it convenient.
  21 # ----------------------------------------------------------
  22 
  23 #  If SUDO_COMMAND variable is not set we are not being run through
  24 #+ sudo, so rerun ourselves. Pass the user's real and group id . . .
  25 
  26 if [ -z "$SUDO_COMMAND" ]
  27 then
  28    mntusr=$(id -u) grpusr=$(id -g) sudo $0 $*
  29    exit 0
  30 fi
  31 
  32 # We will only get here if we are being run by sudo.
  33 /bin/mount $* -o uid=$mntusr,gid=$grpusr
  34 
  35 exit 0
  36 
  37 # Additional notes (from the author of this script): 
  38 # -------------------------------------------------
  39 
  40 # 1) Linux allows the "users" option in the /etc/fstab
  41 #    file so that any user can mount removable media.
  42 #    But, on a server, I like to allow only a few
  43 #    individuals access to removable media.
  44 #    I find using sudo gives me more control.
  45 
  46 # 2) I also find sudo to be more convenient than
  47 #    accomplishing this task through groups.
  48 
  49 # 3) This method gives anyone with proper permissions
  50 #    root access to the mount command, so be careful
  51 #    about who you allow access.
  52 #    You can get finer control over which access can be mounted
  53 #    by using this same technique in separate mntfloppy, mntcdrom,
  54 #    and mntsamba scripts.

Caution

Too many levels of recursion can exhaust the script's stack space, causing a segfault.