Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
Prev | Chapter 36. Miscellany | Next

# 36.4. Recursion: a script calling itself

Can a script recursively call itself? Indeed.

Example 36-10. 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 36-11. 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               #+ non-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 36-12. Another (useful) script that recursively calls itself

       1 #!/bin/bash
       2 # usrmnt.sh, written by Anthony Richardson
       3 # Used in ABS Guide 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.

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

Prev | Home | Next
Tests and Comparisons: Alternatives | Up | "Colorizing" Scripts