Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Recursion

Can a script recursively call itself? Indeed.

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

#!/bin/bash
 # recurse.sh
 
 #  Can a script recursively call itself?
 #  Yes, but is this of any practical use?
 #  (See the following.)
 
 RANGE=10
 MAXVAL=9
 
 i=$RANDOM
 let "i %= $RANGE"  # Generate a random number between 0 and $RANGE - 1.
 
 if [ "$i" -lt "$MAXVAL" ]
 then
   echo "i = $i"
   ./$0             #  Script recursively spawns a new instance of itself.
 fi                 #  Each child script does the same, until
                    #+ a generated $i equals $MAXVAL.
 
 #  Using a "while" loop instead of an "if/then" test causes problems.
 #  Explain why.
 
 exit 0
 
 # Note:
 # ----
 # This script must have execute permission for it to work properly.
 # This is the case even if it is invoked by an "sh" command.
 # Explain why.

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

#!/bin/bash
 # pb.sh: phone book
 
 # Written by Rick Boivie, and used with permission.
 # Modifications by ABS Guide author.
 
 MINARGS=1     #  Script needs at least one argument.
 DATAFILE=./phonebook
               #  A data file in current working directory
               #+ named "phonebook" must exist.
 PROGNAME=$0
 E_NOARGS=70   #  No arguments error.
 
 if [ $# -lt $MINARGS ]; then
       echo "Usage: "$PROGNAME" data"
       exit $E_NOARGS
 fi      
 
 
 if [ $# -eq $MINARGS ]; then
       grep $1 "$DATAFILE"
       # 'grep' prints an error message if $DATAFILE not present.
 else
       ( shift; "$PROGNAME" $* ) | grep $1
       # Script recursively calls itself.
 fi
 
 exit 0        #  Script exits here.
               #  Therefore, it's o.k. to put
 	      #+ non-hashmarked comments and data after this point.
 
 # ------------------------------------------------------------------------
 Sample "phonebook" datafile:
 
 John Doe        1555 Main St., Baltimore, MD 21228          (410) 222-3333
 Mary Moe        9899 Jones Blvd., Warren, NH 03787          (603) 898-3232
 Richard Roe     856 E. 7th St., New York, NY 10009          (212) 333-4567
 Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678
 Zoe Zenobia     4481 N. Baker St., San Francisco, SF 94338  (415) 501-1631
 # ------------------------------------------------------------------------
 
 $bash pb.sh Roe
 Richard Roe     856 E. 7th St., New York, NY 10009          (212) 333-4567
 Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678
 
 $bash pb.sh Roe Sam
 Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678
 
 #  When more than one argument is passed to this script,
 #+ it prints *only* the line(s) containing all the arguments.

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

#!/bin/bash
 # usrmnt.sh, written by Anthony Richardson
 # Used with permission.
 
 # usage:       usrmnt.sh
 # description: mount device, invoking user must be listed in the
 #              MNTUSERS group in the /etc/sudoers file.
 
 # ----------------------------------------------------------
 #  This is a usermount script that reruns itself using sudo.
 #  A user with the proper permissions only has to type
 
 #   usermount /dev/fd0 /mnt/floppy
 
 # instead of
 
 #   sudo usermount /dev/fd0 /mnt/floppy
 
 #  I use this same technique for all of my
 #+ sudo scripts, because I find it convenient.
 # ----------------------------------------------------------
 
 #  If SUDO_COMMAND variable is not set we are not being run through
 #+ sudo, so rerun ourselves. Pass the user's real and group id . . .
 
 if [ -z "$SUDO_COMMAND" ]
 then
    mntusr=$(id -u) grpusr=$(id -g) sudo $0 $*
    exit 0
 fi
 
 # We will only get here if we are being run by sudo.
 /bin/mount $* -o uid=$mntusr,gid=$grpusr
 
 exit 0
 
 # Additional notes (from the author of this script): 
 # -------------------------------------------------
 
 # 1) Linux allows the "users" option in the /etc/fstab
 #    file so that any user can mount removable media.
 #    But, on a server, I like to allow only a few
 #    individuals access to removable media.
 #    I find using sudo gives me more control.
 
 # 2) I also find sudo to be more convenient than
 #    accomplishing this task through groups.
 
 # 3) This method gives anyone with proper permissions
 #    root access to the mount command, so be careful
 #    about who you allow access.
 #    You can get finer control over which access can be mounted
 #    by using this same technique in separate mntfloppy, mntcdrom,
 #    and mntsamba scripts.

Caution

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

Assorted Tips

  • To keep a record of which user scripts have run during a particular session or over a number of sessions, add the following lines to each script you want to keep track of. This will keep a continuing file record of the script names and invocation times.

    # Append (>>) following to end of each script tracked.
     
     whoami>> $SAVE_FILE    # User invoking the script.
     echo $0>> $SAVE_FILE   # Script name.
     date>> $SAVE_FILE      # Date and time.
     echo>> $SAVE_FILE      # Blank line as separator.
     
     #  Of course, SAVE_FILE defined and exported as environmental variable in ~/.bashrc
     #+ (something like ~/.scripts-run)

  • The >> operator appends lines to a file. What if you wish to prepend a line to an existing file, that is, to paste it in at the beginning?

    file=data.txt
     title="***This is the title line of data text file***"
     
     echo $title | cat - $file >$file.new
     # "cat -" concatenates stdout to $file.
     #  End result is
     #+ to write a new file with $title appended at *beginning*.

    This is a simplified variant of the Example 17-13 script given earlier. And, of course, sed can also do this.

  • A shell script may act as an embedded command inside another shell script, a Tcl or wish script, or even a Makefile. It can be invoked as an external shell command in a C program using the system() call, i.e., system("script_name");.

  • Setting a variable to the contents of an embedded sed or awk script increases the readability of the surrounding shell wrapper. See Example A-1 and Example 11-18.

  • Put together files containing your favorite and most useful definitions and functions. As necessary, "include" one or more of these "library files" in scripts with either the dot (.) or source command.

    # SCRIPT LIBRARY
     # ------ -------
     
     # Note:
     # No "#!" here.
     # No "live code" either.
     
     
     # Useful variable definitions
     
     ROOT_UID=0             # Root has $UID 0.
     E_NOTROOT=101          # Not root user error. 
     MAXRETVAL=255          # Maximum (positive) return value of a function.
     SUCCESS=0
     FAILURE=-1
     
     
     
     # Functions
     
     Usage ()               # "Usage:" message.
     {
       if [ -z "$1" ]       # No arg passed.
       then
         msg=filename
       else
         msg=$@
       fi
     
       echo "Usage: `basename $0` "$msg""
     }  
     
     
     Check_if_root ()       # Check if root running script.
     {                      # From "ex39.sh" example.
       if [ "$UID" -ne "$ROOT_UID" ]
       then
         echo "Must be root to run this script."
         exit $E_NOTROOT
       fi
     }  
     
     
     CreateTempfileName ()  # Creates a "unique" temp filename.
     {                      # From "ex51.sh" example.
       prefix=temp
       suffix=`eval date +%s`
       Tempfilename=$prefix.$suffix
     }
     
     
     isalpha2 ()            # Tests whether *entire string* is alphabetic.
     {                      # From "isalpha.sh" example.
       [ $# -eq 1 ] || return $FAILURE
     
       case $1 in
       *[!a-zA-Z]*|"") return $FAILURE;;
       *) return $SUCCESS;;
       esac                 # Thanks, S.C.
     }
     
     
     abs ()                           # Absolute value.
     {                                # Caution: Max return value = 255.
       E_ARGERR=-999999
     
       if [ -z "$1" ]                 # Need arg passed.
       then
         return $E_ARGERR             # Obvious error value returned.
       fi
     
       if [ "$1" -ge 0 ]              # If non-negative,
       then                           #
         absval=$1                    # stays as-is.
       else                           # Otherwise,
         let "absval = (( 0 - $1 ))"  # change sign.
       fi  
     
       return $absval
     }
     
     
     tolower ()             #  Converts string(s) passed as argument(s)
     {                      #+ to lowercase.
     
       if [ -z "$1" ]       #  If no argument(s) passed,
       then                 #+ send error message
         echo "(null)"      #+ (C-style void-pointer error message)
         return             #+ and return from function.
       fi  
     
       echo "$@" | tr A-Z a-z
       # Translate all passed arguments ($@).
     
       return
     
     # Use command substitution to set a variable to function output.
     # For example:
     #    oldvar="A seT of miXed-caSe LEtTerS"
     #    newvar=`tolower "$oldvar"`
     #    echo "$newvar"    # a set of mixed-case letters
     #
     # Exercise: Rewrite this function to change lowercase passed argument(s)
     #           to uppercase ... toupper()  [easy].
     }

  • Use special-purpose comment headers to increase clarity and legibility in scripts.

    ## Caution.
     rm -rf *.zzy   ##  The "-rf" options to "rm" are very dangerous,
                    ##+ especially with wildcards.
     
     #+ Line continuation.
     #  This is line 1
     #+ of a multi-line comment,
     #+ and this is the final line.
     
     #* Note.
     
     #o List item.
     
     #> Another point of view.
     while [ "$var1" != "end" ]    #> while test "$var1" != "end"

  • A particularly clever use of if-test constructs is commenting out blocks of code.

    #!/bin/bash
     
     COMMENT_BLOCK=
     #  Try setting the above variable to some value
     #+ for an unpleasant surprise.
     
     if [ $COMMENT_BLOCK ]; then
     
     Comment block --
     =================================
     This is a comment line.
     This is another comment line.
     This is yet another comment line.
     =================================
     
     echo "This will not echo."
     
     Comment blocks are error-free! Whee!
     
     fi
     
     echo "No more comments, please."
     
     exit 0

    Compare this with using here documents to comment out code blocks.

  • Using the $? exit status variable, a script may test if a parameter contains only digits, so it can be treated as an integer.

    #!/bin/bash
     
     SUCCESS=0
     E_BADINPUT=65
     
     test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null
     # An integer is either equal to 0 or not equal to 0.
     # 2>/dev/null suppresses error message.
     
     if [ $? -ne "$SUCCESS" ]
     then
       echo "Usage: `basename $0` integer-input"
       exit $E_BADINPUT
     fi
     
     let "sum = $1 + 25"             # Would give error if $1 not integer.
     echo "Sum = $sum"
     
     # Any variable, not just a command line parameter, can be tested this way.
     
     exit 0

  • The 0 - 255 range for function return values is a severe limitation. Global variables and other workarounds are often problematic. An alternative method for a function to communicate a value back to the main body of the script is to have the function write to stdout (usually with echo) the "return value," and assign this to a variable. This is actually a variant of command substitution.

    Example 33-15. Return value trickery

    #!/bin/bash
     # multiplication.sh
     
     multiply ()                     # Multiplies params passed.
     {                               # Will accept a variable number of args.
     
       local product=1
     
       until [ -z "$1" ]             # Until uses up arguments passed...
       do
         let "product *= $1"
         shift
       done
     
       echo $product                 #  Will not echo to stdout,
     }                               #+ since this will be assigned to a variable.
     
     mult1=15383; mult2=25211
     val1=`multiply $mult1 $mult2`
     echo "$mult1 X $mult2 = $val1"
                                     # 387820813
     
     mult1=25; mult2=5; mult3=20
     val2=`multiply $mult1 $mult2 $mult3`
     echo "$mult1 X $mult2 X $mult3 = $val2"
                                     # 2500
     
     mult1=188; mult2=37; mult3=25; mult4=47
     val3=`multiply $mult1 $mult2 $mult3 $mult4`
     echo "$mult1 X $mult2 X $mult3 X $mult4 = $val3"
                                     # 8173300
     
     exit 0

    The same technique also works for alphanumeric strings. This means that a function can "return" a non-numeric value.

    capitalize_ichar ()          #  Capitalizes initial character
     {                            #+ of argument string(s) passed.
     
       string0="$@"               # Accepts multiple arguments.
     
       firstchar=${string0:0:1}   # First character.
       string1=${string0:1}       # Rest of string(s).
     
       FirstChar=`echo "$firstchar" | tr a-z A-Z`
                                  # Capitalize first character.
     
       echo "$FirstChar$string1"  # Output to stdout.
     
     }  
     
     newstring=`capitalize_ichar "every sentence should start with a capital letter."`
     echo "$newstring"          # Every sentence should start with a capital letter.

    It is even possible for a function to "return" multiple values with this method.

    Example 33-16. Even more return value trickery

    #!/bin/bash
     # sum-product.sh
     # A function may "return" more than one value.
     
     sum_and_product ()   # Calculates both sum and product of passed args.
     {
       echo $(( $1 + $2 )) $(( $1 * $2 ))
     # Echoes to stdout each calculated value, separated by space.
     }
     
     echo
     echo "Enter first number "
     read first
     
     echo
     echo "Enter second number "
     read second
     echo
     
     retval=`sum_and_product $first $second`      # Assigns output of function.
     sum=`echo "$retval" | awk '{print $1}'`      # Assigns first field.
     product=`echo "$retval" | awk '{print $2}'`  # Assigns second field.
     
     echo "$first + $second = $sum"
     echo "$first * $second = $product"
     echo
     
     exit 0
  • Next in our bag of trick are techniques for passing an array to a function, then "returning" an array back to the main body of the script.

    Passing an array involves loading the space-separated elements of the array into a variable with command substitution. Getting an array back as the "return value" from a function uses the previously mentioned strategem of echoing the array in the function, then invoking command substitution and the ( ... ) operator to assign it to an array.

    Example 33-17. Passing and returning arrays

    #!/bin/bash
     # array-function.sh: Passing an array to a function and...
     #                   "returning" an array from a function
     
     
     Pass_Array ()
     {
       local passed_array   # Local variable.
       passed_array=( `echo "$1"` )
       echo "${passed_array[@]}"
       #  List all the elements of the new array
       #+ declared and set within the function.
     }
     
     
     original_array=( element1 element2 element3 element4 element5 )
     
     echo
     echo "original_array = ${original_array[@]}"
     #                      List all elements of original array.
     
     
     # This is the trick that permits passing an array to a function.
     # **********************************
     argument=`echo ${original_array[@]}`
     # **********************************
     #  Pack a variable
     #+ with all the space-separated elements of the original array.
     #
     # Note that attempting to just pass the array itself will not work.
     
     
     # This is the trick that allows grabbing an array as a "return value".
     # *****************************************
     returned_array=( `Pass_Array "$argument"` )
     # *****************************************
     # Assign 'echoed' output of function to array variable.
     
     echo "returned_array = ${returned_array[@]}"
     
     echo "============================================================="
     
     #  Now, try it again,
     #+ attempting to access (list) the array from outside the function.
     Pass_Array "$argument"
     
     # The function itself lists the array, but...
     #+ accessing the array from outside the function is forbidden.
     echo "Passed array (within function) = ${passed_array[@]}"
     # NULL VALUE since this is a variable local to the function.
     
     echo
     
     exit 0

    For a more elaborate example of passing arrays to functions, see Example A-10.

  • Using the double parentheses construct, it is possible to use C-like syntax for setting and incrementing variables and in for and while loops. See Example 10-12 and Example 10-17.

  • Setting the path and umask at the beginning of a script makes it more "portable" -- more likely to run on a "foreign" machine whose user may have bollixed up the $PATH and umask.
    #!/bin/bash
     PATH=/bin:/usr/bin:/usr/local/bin ; export PATH
     umask 022   # Files that the script creates will have 755 permission.
     
     # Thanks to Ian D. Allen, for this tip.

  • A useful scripting technique is to repeatedly feed the output of a filter (by piping) back to the same filter, but with a different set of arguments and/or options. Especially suitable for this are tr and grep.

    # From "wstrings.sh" example.
     
     wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \
     tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

    Example 33-18. Fun with anagrams

    #!/bin/bash
     # agram.sh: Playing games with anagrams.
     
     # Find anagrams of...
     LETTERSET=etaoinshrdlu
     FILTER='.......'       # How many letters minimum?
     #       1234567
     
     anagram "$LETTERSET" | # Find all anagrams of the letterset...
     grep "$FILTER" |       # With at least 7 letters,
     grep '^is' |           # starting with 'is'
     grep -v 's$' |         # no plurals
     grep -v 'ed$'          # no past tense verbs
     # Possible to add many combinations of conditions and filters.
     
     #  Uses "anagram" utility
     #+ that is part of the author's "yawl" word list package.
     #  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
     #  http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz
     
     exit 0                 # End of code.
     
     
     bash$ sh agram.sh
     islander
     isolate
     isolead
     isotheral
     
     
     
     #  Exercises:
     #  ---------
     #  Modify this script to take the LETTERSET as a command-line parameter.
     #  Parameterize the filters in lines 11 - 13 (as with $FILTER),
     #+ so that they can be specified by passing arguments to a function.
     
     #  For a slightly different approach to anagramming,
     #+ see the agram2.sh script.

    See also Example 27-3, Example 12-22, and Example A-9.

  • Use "anonymous here documents" to comment out blocks of code, to save having to individually comment out each line with a #. See Example 17-11.

  • Running a script on a machine that relies on a command that might not be installed is dangerous. Use whatis to avoid potential problems with this.

    CMD=command1                 # First choice.
     PlanB=command2               # Fallback option.
     
     command_test=$(whatis "$CMD" | grep 'nothing appropriate')
     #  If 'command1' not found on system , 'whatis' will return
     #+ "command1: nothing appropriate."
     #
     #  A safer alternative is:
     #     command_test=$(whereis "$CMD" | grep \/)
     #  But then the sense of the following test would have to be reversed,
     #+ since the $command_test variable holds content only if
     #+ the $CMD exists on the system.
     #     (Thanks, bojster.)
     
     
     if [[ -z "$command_test" ]]  # Check whether command present.
     then
       $CMD option1 option2       #  Run command1 with options.
     else                         #  Otherwise,
       $PlanB                     #+ run command2. 
     fi

  • An if-grep test may not return expected results in an error case, when text is output to stderr, rather that stdout.
    if ls -l nonexistent_filename | grep -q 'No such file or directory'
       then echo "File \"nonexistent_filename\" does not exist."
     fi

    Redirecting stderr to stdout fixes this.
    if ls -l nonexistent_filename 2>&1 | grep -q 'No such file or directory'
     #                             ^^^^
       then echo "File \"nonexistent_filename\" does not exist."
     fi
     
     # Thanks, Chris Martin, for pointing this out.

  • The run-parts command is handy for running a set of command scripts in sequence, particularly in combination with cron or at.

  • It would be nice to be able to invoke X-Windows widgets from a shell script. There happen to exist several packages that purport to do so, namely Xscript, Xmenu, and widtools. The first two of these no longer seem to be maintained. Fortunately, it is still possible to obtain widtools here.

    Caution

    The widtools (widget tools) package requires the XForms library to be installed. Additionally, the Makefile needs some judicious editing before the package will build on a typical Linux system. Finally, three of the six widgets offered do not work (and, in fact, segfault).

    The dialog family of tools offers a method of calling "dialog" widgets from a shell script. The original dialog utility works in a text console, but its successors, gdialog, Xdialog, and kdialog use X-Windows-based widget sets.

    Example 33-19. Widgets invoked from a shell script

    #!/bin/bash
     # dialog.sh: Using 'gdialog' widgets.
     # Must have 'gdialog' installed on your system to run this script.
     # Version 1.1 (corrected 04/05/05)
     
     # This script was inspired by the following article.
     #     "Scripting for X Productivity," by Marco Fioretti,
     #      LINUX JOURNAL, Issue 113, September 2003, pp. 86-9.
     # Thank you, all you good people at LJ.
     
     
     # Input error in dialog box.
     E_INPUT=65
     # Dimensions of display, input widgets.
     HEIGHT=50
     WIDTH=60
     
     # Output file name (constructed out of script name).
     OUTFILE=$0.output
     
     # Display this script in a text widget.
     gdialog --title "Displaying: $0" --textbox $0 $HEIGHT $WIDTH
     
     
     
     # Now, we'll try saving input in a file.
     echo -n "VARIABLE=" > $OUTFILE
     gdialog --title "User Input" --inputbox "Enter variable, please:" \
     $HEIGHT $WIDTH 2>> $OUTFILE
     
     
     if [ "$?" -eq 0 ]
     # It's good practice to check exit status.
     then
       echo "Executed \"dialog box\" without errors."
     else
       echo "Error(s) in \"dialog box\" execution."
             # Or, clicked on "Cancel", instead of "OK" button.
       rm $OUTFILE
       exit $E_INPUT
     fi
     
     
     
     # Now, we'll retrieve and display the saved variable.
     . $OUTFILE   # 'Source' the saved file.
     echo "The variable input in the \"input box\" was: "$VARIABLE""
     
     
     rm $OUTFILE  # Clean up by removing the temp file.
                  # Some applications may need to retain this file.
     
     exit $?

    Bash, version2

    The current version of Bash, the one you have running on your machine, is version 2.xx.y or 3.xx.y.
    bash$ echo $BASH_VERSION
     2.05.b.0(1)-release
     	      
    The version 2 update of the classic Bash scripting language added array variables, [1] string and parameter expansion, and a better method of indirect variable references, among other features.

    Example 34-1. String expansion

    #!/bin/bash
     
     # String expansion.
     # Introduced with version 2 of Bash.
     
     #  Strings of the form $'xxx'
     #+ have the standard escaped characters interpreted. 
     
     echo $'Ringing bell 3 times \a \a \a'
          # May only ring once with certain terminals.
     echo $'Three form feeds \f \f \f'
     echo $'10 newlines \n\n\n\n\n\n\n\n\n\n'
     echo $'\102\141\163\150'   # Bash
                                # Octal equivalent of characters.
     
     exit 0

    Example 34-2. Indirect variable references - the new way

    #!/bin/bash
     
     # Indirect variable referencing.
     # This has a few of the attributes of references in C++.
     
     
     a=letter_of_alphabet
     letter_of_alphabet=z
     
     echo "a = $a"           # Direct reference.
     
     echo "Now a = ${!a}"    # Indirect reference.
     # The ${!variable} notation is greatly superior to the old "eval var1=\$$var2"
     
     echo
     
     t=table_cell_3
     table_cell_3=24
     echo "t = ${!t}"                      # t = 24
     table_cell_3=387
     echo "Value of t changed to ${!t}"    # 387
     
     #  This is useful for referencing members of an array or table,
     #+ or for simulating a multi-dimensional array.
     #  An indexing option (analogous to pointer arithmetic)
     #+ would have been nice. Sigh.
     
     exit 0

    Example 34-3. Simple database application, using indirect variable referencing

    #!/bin/bash
     # resistor-inventory.sh
     # Simple database application using indirect variable referencing.
     
     # ============================================================== #
     # Data
     
     B1723_value=470                                   # Ohms
     B1723_powerdissip=.25                             # Watts
     B1723_colorcode="yellow-violet-brown"             # Color bands
     B1723_loc=173                                     # Where they are
     B1723_inventory=78                                # How many
     
     B1724_value=1000
     B1724_powerdissip=.25
     B1724_colorcode="brown-black-red"
     B1724_loc=24N
     B1724_inventory=243
     
     B1725_value=10000
     B1725_powerdissip=.25
     B1725_colorcode="brown-black-orange"
     B1725_loc=24N
     B1725_inventory=89
     
     # ============================================================== #
     
     
     echo
     
     PS3='Enter catalog number: '
     
     echo
     
     select catalog_number in "B1723" "B1724" "B1725"
     do
       Inv=${catalog_number}_inventory
       Val=${catalog_number}_value
       Pdissip=${catalog_number}_powerdissip
       Loc=${catalog_number}_loc
       Ccode=${catalog_number}_colorcode
     
       echo
       echo "Catalog number $catalog_number:"
       echo "There are ${!Inv} of [${!Val} ohm / ${!Pdissip} watt] resistors in stock."
       echo "These are located in bin # ${!Loc}."
       echo "Their color code is \"${!Ccode}\"."
     
       break
     done
     
     echo; echo
     
     # Exercises:
     # ---------
     # 1) Rewrite this script to read its data from an external file.
     # 2) Rewrite this script to use arrays,
     #+   rather than indirect variable referencing.
     #    Which method is more straightforward and intuitive?
     
     
     # Notes:
     # -----
     #  Shell scripts are inappropriate for anything except the most simple
     #+ database applications, and even then it involves workarounds and kludges.
     #  Much better is to use a language with native support for data structures,
     #+ such as C++ or Java (or even Perl).
     
     exit 0

    Example 34-4. Using arrays and other miscellaneous trickery to deal four random hands from a deck of cards

    #!/bin/bash
     
     # Cards:
     # Deals four random hands from a deck of cards.
     
     UNPICKED=0
     PICKED=1
     
     DUPE_CARD=99
     
     LOWER_LIMIT=0
     UPPER_LIMIT=51
     CARDS_IN_SUIT=13
     CARDS=52
     
     declare -a Deck
     declare -a Suits
     declare -a Cards
     #  It would have been easier to implement and more intuitive
     #+ with a single, 3-dimensional array.
     #  Perhaps a future version of Bash will support multidimensional arrays.
     
     
     initialize_Deck ()
     {
     i=$LOWER_LIMIT
     until [ "$i" -gt $UPPER_LIMIT ]
     do
       Deck[i]=$UNPICKED   # Set each card of "Deck" as unpicked.
       let "i += 1"
     done
     echo
     }
     
     initialize_Suits ()
     {
     Suits[0]=C #Clubs
     Suits[1]=D #Diamonds
     Suits[2]=H #Hearts
     Suits[3]=S #Spades
     }
     
     initialize_Cards ()
     {
     Cards=(2 3 4 5 6 7 8 9 10 J Q K A)
     # Alternate method of initializing an array.
     }
     
     pick_a_card ()
     {
     card_number=$RANDOM
     let "card_number %= $CARDS"
     if [ "${Deck[card_number]}" -eq $UNPICKED ]
     then
       Deck[card_number]=$PICKED
       return $card_number
     else  
       return $DUPE_CARD
     fi
     }
     
     parse_card ()
     {
     number=$1
     let "suit_number = number / CARDS_IN_SUIT"
     suit=${Suits[suit_number]}
     echo -n "$suit-"
     let "card_no = number % CARDS_IN_SUIT"
     Card=${Cards[card_no]}
     printf %-4s $Card
     # Print cards in neat columns.
     }
     
     seed_random ()  # Seed random number generator.
     {               # What happens if you don't do this?
     seed=`eval date +%s`
     let "seed %= 32766"
     RANDOM=$seed
     #  What are some other methods
     #+ of seeding the random number generator?
     }
     
     deal_cards ()
     {
     echo
     
     cards_picked=0
     while [ "$cards_picked" -le $UPPER_LIMIT ]
     do
       pick_a_card
       t=$?
     
       if [ "$t" -ne $DUPE_CARD ]
       then
         parse_card $t
     
         u=$cards_picked+1
         # Change back to 1-based indexing (temporarily). Why?
         let "u %= $CARDS_IN_SUIT"
         if [ "$u" -eq 0 ]   # Nested if/then condition test.
         then
          echo
          echo
         fi
         # Separate hands.
     
         let "cards_picked += 1"
       fi  
     done  
     
     echo
     
     return 0
     }
     
     
     # Structured programming:
     # Entire program logic modularized in functions.
     
     #================
     seed_random
     initialize_Deck
     initialize_Suits
     initialize_Cards
     deal_cards
     #================
     
     exit 0
     
     
     
     # Exercise 1:
     # Add comments to thoroughly document this script.
     
     # Exercise 2:
     # Add a routine (function) to print out each hand sorted in suits.
     # You may add other bells and whistles if you like.
     
     # Exercise 3:
     # Simplify and streamline the logic of the script.

    A Sample .bashrc File

    The ~/.bashrc file determines the behavior of interactive shells. A good look at this file can lead to a better understanding of Bash.

    Emmanuel Rouat contributed the following very elaborate .bashrc file, written for a Linux system. He welcomes reader feedback on it.

    Study the file carefully, and feel free to reuse code snippets and functions from it in your own .bashrc file or even in your scripts.

    Example K-1. Sample .bashrc file

    #===============================================================
     #
     # PERSONAL $HOME/.bashrc FILE for bash-2.05a (or later)
     #
     # Last modified: Tue Apr 15 20:32:34 CEST 2003
     #
     # This file is read (normally) by interactive shells only.
     # Here is the place to define your aliases, functions and
     # other interactive features like your prompt.
     #
     # This file was designed (originally) for Solaris but based 
     # on Redhat's default .bashrc file
     # --> Modified for Linux.
     # The majority of the code you'll find here is based on code found
     # on Usenet (or internet).
     # This bashrc file is a bit overcrowded - remember it is just
     # just an example. Tailor it to your needs
     #
     #
     #===============================================================
     
     # --> Comments added by HOWTO author.
     # --> And then edited again by ER :-)
     
     #-----------------------------------
     # Source global definitions (if any)
     #-----------------------------------
     
     if [ -f /etc/bashrc ]; then
             . /etc/bashrc   # --> Read /etc/bashrc, if present.
     fi
     
     #-------------------------------------------------------------
     # Automatic setting of $DISPLAY (if not set already)
     # This works for linux - your mileage may vary.... 
     # The problem is that different types of terminals give
     # different answers to 'who am i'......
     # I have not found a 'universal' method yet
     #-------------------------------------------------------------
     
     function get_xserver ()
     {
         case $TERM in
     	xterm )
                 XSERVER=$(who am i | awk '{print $NF}' | tr -d ')''(' ) 
                 # Ane-Pieter Wieringa suggests the following alternative:
                 # I_AM=$(who am i)
                 # SERVER=${I_AM#*(}
                 # SERVER=${SERVER%*)}
     
                 XSERVER=${XSERVER%%:*}
     	    ;;
     	aterm | rxvt)
      	# find some code that works here.....
     	    ;;
         esac  
     }
     
     if [ -z ${DISPLAY:=""} ]; then
         get_xserver
         if [[ -z ${XSERVER}  || ${XSERVER} == $(hostname) || ${XSERVER} == "unix" ]]; then 
     	DISPLAY=":0.0"		# Display on local host
         else		
     	DISPLAY=${XSERVER}:0.0	# Display on remote host
         fi
     fi
     
     export DISPLAY
     
     #---------------
     # Some settings
     #---------------
     
     ulimit -S -c 0		# Don't want any coredumps
     set -o notify
     set -o noclobber
     set -o ignoreeof
     set -o nounset
     #set -o xtrace          # useful for debuging
     
     # Enable options:
     shopt -s cdspell
     shopt -s cdable_vars
     shopt -s checkhash
     shopt -s checkwinsize
     shopt -s mailwarn
     shopt -s sourcepath
     shopt -s no_empty_cmd_completion  # bash>=2.04 only
     shopt -s cmdhist
     shopt -s histappend histreedit histverify
     shopt -s extglob	# necessary for programmable completion
     
     # Disable options:
     shopt -u mailwarn
     unset MAILCHECK		# I don't want my shell to warn me of incoming mail
     
     
     export TIMEFORMAT=$'\nreal %3R\tuser %3U\tsys %3S\tpcpu %P\n'
     export HISTIGNORE="&:bg:fg:ll:h"
     export HOSTFILE=$HOME/.hosts	# Put a list of remote hosts in ~/.hosts
     
     
     
     #-----------------------
     # Greeting, motd etc...
     #-----------------------
     
     # Define some colors first:
     red='\e[0;31m'
     RED='\e[1;31m'
     blue='\e[0;34m'
     BLUE='\e[1;34m'
     cyan='\e[0;36m'
     CYAN='\e[1;36m'
     NC='\e[0m'              # No Color
     # --> Nice. Has the same effect as using "ansi.sys" in DOS.
     
     # Looks best on a black background.....
     echo -e "${CYAN}This is BASH ${RED}${BASH_VERSION%.*}${CYAN} - DISPLAY on ${RED}$DISPLAY${NC}\n"
     date
     if [ -x /usr/games/fortune ]; then
         /usr/games/fortune -s     # makes our day a bit more fun.... :-)
     fi
     
     function _exit()	# function to run upon exit of shell
     {
         echo -e "${RED}Hasta la vista, baby${NC}"
     }
     trap _exit EXIT
     
     #---------------
     # Shell Prompt
     #---------------
     
     if [[ "${DISPLAY#$HOST}" != ":0.0" &&  "${DISPLAY}" != ":0" ]]; then  
         HILIT=${red}   # remote machine: prompt will be partly red
     else
         HILIT=${cyan}  # local machine: prompt will be partly cyan
     fi
     
     #  --> Replace instances of \W with \w in prompt functions below
     #+ --> to get display of full path name.
     
     function fastprompt()
     {
         unset PROMPT_COMMAND
         case $TERM in
             *term | rxvt )
                 PS1="${HILIT}[\h]$NC \W > \[\033]0;\${TERM} [\u@\h] \w\007\]" ;;
     	linux )
     	    PS1="${HILIT}[\h]$NC \W > " ;;
             *)
                 PS1="[\h] \W > " ;;
         esac
     }
     
     function powerprompt()
     {
         _powerprompt()
         {
             LOAD=$(uptime|sed -e "s/.*: \([^,]*\).*/\1/" -e "s/ //g")
         }
     
         PROMPT_COMMAND=_powerprompt
         case $TERM in
             *term | rxvt  )
                 PS1="${HILIT}[\A \$LOAD]$NC\n[\h \#] \W > \[\033]0;\${TERM} [\u@\h] \w\007\]" ;;
             linux )
                 PS1="${HILIT}[\A - \$LOAD]$NC\n[\h \#] \w > " ;;
             * )
                 PS1="[\A - \$LOAD]\n[\h \#] \w > " ;;
         esac
     }
     
     powerprompt     # this is the default prompt - might be slow
                     # If too slow, use fastprompt instead....
     
     #===============================================================
     #
     # ALIASES AND FUNCTIONS
     #
     # Arguably, some functions defined here are quite big
     # (ie 'lowercase') but my workstation has 512Meg of RAM, so .....
     # If you want to make this file smaller, these functions can
     # be converted into scripts.
     #
     # Many functions were taken (almost) straight from the bash-2.04
     # examples.
     #
     #===============================================================
     
     #-------------------
     # Personnal Aliases
     #-------------------
     
     alias rm='rm -i'
     alias cp='cp -i'
     alias mv='mv -i'
     # -> Prevents accidentally clobbering files.
     alias mkdir='mkdir -p'
     
     alias h='history'
     alias j='jobs -l'
     alias r='rlogin'
     alias which='type -all'
     alias ..='cd ..'
     alias path='echo -e ${PATH//:/\\n}'
     alias print='/usr/bin/lp -o nobanner -d $LPDEST'   # Assumes LPDEST is defined
     alias pjet='enscript -h -G -fCourier9 -d $LPDEST'  # Pretty-print using enscript
     alias background='xv -root -quit -max -rmode 5'    # Put a picture in the background
     alias du='du -kh'
     alias df='df -kTh'
     
     # The 'ls' family (this assumes you use the GNU ls)
     alias la='ls -Al'               # show hidden files
     alias ls='ls -hF --color'	# add colors for filetype recognition
     alias lx='ls -lXB'              # sort by extension
     alias lk='ls -lSr'              # sort by size
     alias lc='ls -lcr'		# sort by change time  
     alias lu='ls -lur'		# sort by access time   
     alias lr='ls -lR'               # recursive ls
     alias lt='ls -ltr'              # sort by date
     alias lm='ls -al |more'         # pipe through 'more'
     alias tree='tree -Csu'		# nice alternative to 'ls'
     
     # tailoring 'less'
     alias more='less'
     export PAGER=less
     export LESSCHARSET='latin1'
     export LESSOPEN='|/usr/bin/lesspipe.sh %s 2>&-' # Use this if lesspipe.sh exists
     export LESS='-i -N -w  -z-4 -g -e -M -X -F -R -P%t?f%f \
     :stdin .?pb%pb\%:?lbLine %lb:?bbByte %bb:-...'
     
     # spelling typos - highly personnal :-)
     alias xs='cd'
     alias vf='cd'
     alias moer='more'
     alias moew='more'
     alias kk='ll'
     
     #----------------
     # a few fun ones
     #----------------
     
     function xtitle ()
     {
         case "$TERM" in
             *term | rxvt)
                 echo -n -e "\033]0;$*\007" ;;
             *)  
     	    ;;
         esac
     }
     
     # aliases...
     alias top='xtitle Processes on $HOST && top'
     alias make='xtitle Making $(basename $PWD) ; make'
     alias ncftp="xtitle ncFTP ; ncftp"
     
     # .. and functions
     function man ()
     {
         for i ; do
     	xtitle The $(basename $1|tr -d .[:digit:]) manual
     	command man -F -a "$i"
         done
     }
     
     function ll(){ ls -l "$@"| egrep "^d" ; ls -lXB "$@" 2>&-| egrep -v "^d|total "; }
     function te()  # wrapper around xemacs/gnuserv
     {
         if [ "$(gnuclient -batch -eval t 2>&-)" == "t" ]; then
             gnuclient -q "$@";
         else
             ( xemacs "$@" &);
         fi
     }
     
     #-----------------------------------
     # File & strings related functions:
     #-----------------------------------
     
     # Find a file with a pattern in name:
     function ff() { find . -type f -iname '*'$*'*' -ls ; }
     # Find a file with pattern $1 in name and Execute $2 on it:
     function fe() { find . -type f -iname '*'$1'*' -exec "${2:-file}" {} \;  ; }
     # find pattern in a set of filesand highlight them:
     function fstr()
     {
         OPTIND=1
         local case=""
         local usage="fstr: find string in files.
     Usage: fstr [-i] \"pattern\" [\"filename pattern\"] "
         while getopts :it opt
         do
             case "$opt" in
             i) case="-i " ;;
             *) echo "$usage"; return;;
             esac
         done
         shift $(( $OPTIND - 1 ))
         if [ "$#" -lt 1 ]; then
             echo "$usage"
             return;
         fi
         local SMSO=$(tput smso)
         local RMSO=$(tput rmso)
         find . -type f -name "${2:-*}" -print0 | xargs -0 grep -sn ${case} "$1" 2>&- | \
     sed "s/$1/${SMSO}\0${RMSO}/gI" | more
     }
     
     function cuttail() # cut last n lines in file, 10 by default
     {
         nlines=${2:-10}
         sed -n -e :a -e "1,${nlines}!{P;N;D;};N;ba" $1
     }
     
     function lowercase()  # move filenames to lowercase
     {
         for file ; do
             filename=${file##*/}
             case "$filename" in
             */*) dirname==${file%/*} ;;
             *) dirname=.;;
             esac
             nf=$(echo $filename | tr A-Z a-z)
             newname="${dirname}/${nf}"
             if [ "$nf" != "$filename" ]; then
                 mv "$file" "$newname"
                 echo "lowercase: $file --> $newname"
             else
                 echo "lowercase: $file not changed."
             fi
         done
     }
     
     function swap()         # swap 2 filenames around
     {
         local TMPFILE=tmp.$$
         mv "$1" $TMPFILE
         mv "$2" "$1"
         mv $TMPFILE "$2"
     }
     
     
     #-----------------------------------
     # Process/system related functions:
     #-----------------------------------
     
     function my_ps() { ps $@ -u $USER -o pid,%cpu,%mem,bsdtime,command ; }
     function pp() { my_ps f | awk '!/awk/ && $0~var' var=${1:-".*"} ; }
     
     # This function is roughly the same as 'killall' on linux
     # but has no equivalent (that I know of) on Solaris
     function killps()   # kill by process name
     {
         local pid pname sig="-TERM"   # default signal
         if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
             echo "Usage: killps [-SIGNAL] pattern"
             return;
         fi
         if [ $# = 2 ]; then sig=$1 ; fi
         for pid in $(my_ps| awk '!/awk/ && $0~pat { print $1 }' pat=${!#} ) ; do
             pname=$(my_ps | awk '$1~var { print $5 }' var=$pid )
             if ask "Kill process $pid <$pname> with signal $sig?"
                 then kill $sig $pid
             fi
         done
     }
     
     function my_ip() # get IP adresses
     {
         MY_IP=$(/sbin/ifconfig ppp0 | awk '/inet/ { print $2 } ' | sed -e s/addr://)
         MY_ISP=$(/sbin/ifconfig ppp0 | awk '/P-t-P/ { print $3 } ' | sed -e s/P-t-P://)
     }
     
     function ii()   # get current host related info
     {
         echo -e "\nYou are logged on ${RED}$HOST"
         echo -e "\nAdditionnal information:$NC " ; uname -a
         echo -e "\n${RED}Users logged on:$NC " ; w -h
         echo -e "\n${RED}Current date :$NC " ; date
         echo -e "\n${RED}Machine stats :$NC " ; uptime
         echo -e "\n${RED}Memory stats :$NC " ; free
         my_ip 2>&- ;
         echo -e "\n${RED}Local IP Address :$NC" ; echo ${MY_IP:-"Not connected"}
         echo -e "\n${RED}ISP Address :$NC" ; echo ${MY_ISP:-"Not connected"}
         echo
     }
     
     # Misc utilities:
     
     function repeat()       # repeat n times command
     {
         local i max
         max=$1; shift;
         for ((i=1; i <= max ; i++)); do  # --> C-like syntax
             eval "$@";
         done
     }
     
     function ask()
     {
         echo -n "$@" '[y/n] ' ; read ans
         case "$ans" in
             y*|Y*) return 0 ;;
             *) return 1 ;;
         esac
     }
     
     #=========================================================================
     #
     # PROGRAMMABLE COMPLETION - ONLY SINCE BASH-2.04
     # Most are taken from the bash 2.05 documentation and from Ian McDonalds
     # 'Bash completion' package (http://www.caliban.org/bash/index.shtml#completion)
     # You will in fact need bash-2.05a for some features
     #
     #=========================================================================
     
     if [ "${BASH_VERSION%.*}" \< "2.05" ]; then
         echo "You will need to upgrade to version 2.05 for programmable completion"
         return
     fi
     
     shopt -s extglob        # necessary
     set +o nounset          # otherwise some completions will fail
     
     complete -A hostname   rsh rcp telnet rlogin r ftp ping disk
     complete -A export     printenv
     complete -A variable   export local readonly unset
     complete -A enabled    builtin
     complete -A alias      alias unalias
     complete -A function   function
     complete -A user       su mail finger
     
     complete -A helptopic  help     # currently same as builtins
     complete -A shopt      shopt
     complete -A stopped -P '%' bg
     complete -A job -P '%'     fg jobs disown
     
     complete -A directory  mkdir rmdir
     complete -A directory   -o default cd
     
     # Compression
     complete -f -o default -X '*.+(zip|ZIP)'  zip
     complete -f -o default -X '!*.+(zip|ZIP)' unzip
     complete -f -o default -X '*.+(z|Z)'      compress
     complete -f -o default -X '!*.+(z|Z)'     uncompress
     complete -f -o default -X '*.+(gz|GZ)'    gzip
     complete -f -o default -X '!*.+(gz|GZ)'   gunzip
     complete -f -o default -X '*.+(bz2|BZ2)'  bzip2
     complete -f -o default -X '!*.+(bz2|BZ2)' bunzip2
     # Postscript,pdf,dvi.....
     complete -f -o default -X '!*.ps'  gs ghostview ps2pdf ps2ascii
     complete -f -o default -X '!*.dvi' dvips dvipdf xdvi dviselect dvitype
     complete -f -o default -X '!*.pdf' acroread pdf2ps
     complete -f -o default -X '!*.+(pdf|ps)' gv
     complete -f -o default -X '!*.texi*' makeinfo texi2dvi texi2html texi2pdf
     complete -f -o default -X '!*.tex' tex latex slitex
     complete -f -o default -X '!*.lyx' lyx
     complete -f -o default -X '!*.+(htm*|HTM*)' lynx html2ps
     # Multimedia
     complete -f -o default -X '!*.+(jp*g|gif|xpm|png|bmp)' xv gimp
     complete -f -o default -X '!*.+(mp3|MP3)' mpg123 mpg321
     complete -f -o default -X '!*.+(ogg|OGG)' ogg123
     
     
     
     complete -f -o default -X '!*.pl'  perl perl5
     
     # This is a 'universal' completion function - it works when commands have
     # a so-called 'long options' mode , ie: 'ls --all' instead of 'ls -a'
     
     _get_longopts () 
     { 
         $1 --help | sed  -e '/--/!d' -e 's/.*--\([^[:space:].,]*\).*/--\1/'| \
     grep ^"$2" |sort -u ;
     }
     
     _longopts_func ()
     {
         case "${2:-*}" in
     	-*)	;;
     	*)	return ;;
         esac
     
         case "$1" in
     	\~*)	eval cmd="$1" ;;
     	*)	cmd="$1" ;;
         esac
         COMPREPLY=( $(_get_longopts ${1} ${2} ) )
     }
     complete  -o default -F _longopts_func configure bash
     complete  -o default -F _longopts_func wget id info a2ps ls recode
     
     
     _make_targets ()
     {
         local mdef makef gcmd cur prev i
     
         COMPREPLY=()
         cur=${COMP_WORDS[COMP_CWORD]}
         prev=${COMP_WORDS[COMP_CWORD-1]}
     
         # if prev argument is -f, return possible filename completions.
         # we could be a little smarter here and return matches against
         # `makefile Makefile *.mk', whatever exists
         case "$prev" in
             -*f)    COMPREPLY=( $(compgen -f $cur ) ); return 0;;
         esac
     
         # if we want an option, return the possible posix options
         case "$cur" in
             -)      COMPREPLY=(-e -f -i -k -n -p -q -r -S -s -t); return 0;;
         esac
     
         # make reads `makefile' before `Makefile'
         if [ -f makefile ]; then
             mdef=makefile
         elif [ -f Makefile ]; then
             mdef=Makefile
         else
             mdef=*.mk               # local convention
         fi
     
         # before we scan for targets, see if a makefile name was specified
         # with -f
         for (( i=0; i < ${#COMP_WORDS[@]}; i++ )); do
             if [[ ${COMP_WORDS[i]} == -*f ]]; then
                 eval makef=${COMP_WORDS[i+1]}       # eval for tilde expansion
                 break
             fi
         done
     
             [ -z "$makef" ] && makef=$mdef
     
         # if we have a partial word to complete, restrict completions to
         # matches of that word
         if [ -n "$2" ]; then gcmd='grep "^$2"' ; else gcmd=cat ; fi
     
         # if we don't want to use *.mk, we can take out the cat and use
         # test -f $makef and input redirection
         COMPREPLY=( $(cat $makef 2>/dev/null | awk 'BEGIN {FS=":"} /^[^.#   ][^=]*:/ {print $1}' | tr -s ' ' '\012' | sort -u | eval $gcmd ) )
     }
     
     complete -F _make_targets -X '+($*|*.[cho])' make gmake pmake
     
     
     # cvs(1) completion
     _cvs ()
     {
         local cur prev
         COMPREPLY=()
         cur=${COMP_WORDS[COMP_CWORD]}
         prev=${COMP_WORDS[COMP_CWORD-1]}
     
         if [ $COMP_CWORD -eq 1 ] || [ "${prev:0:1}" = "-" ]; then
             COMPREPLY=( $( compgen -W 'add admin checkout commit diff \
             export history import log rdiff release remove rtag status \
             tag update' $cur ))
         else
             COMPREPLY=( $( compgen -f $cur ))
         fi
         return 0
     }
     complete -F _cvs cvs
     
     _killall ()
     {
         local cur prev
         COMPREPLY=()
         cur=${COMP_WORDS[COMP_CWORD]}
     
         # get a list of processes (the first sed evaluation
         # takes care of swapped out processes, the second
         # takes care of getting the basename of the process)
         COMPREPLY=( $( /usr/bin/ps -u $USER -o comm  | \
             sed -e '1,1d' -e 's#[]\[]##g' -e 's#^.*/##'| \
             awk '{if ($0 ~ /^'$cur'/) print $0}' ))
     
         return 0
     }
     
     complete -F _killall killall killps
     
     
     # A meta-command completion function for commands like sudo(8), which need to
     # first complete on a command, then complete according to that command's own
     # completion definition - currently not quite foolproof (e.g. mount and umount
     # don't work properly), but still quite useful - By Ian McDonald, modified by me.
     
     _my_command()
     {
         local cur func cline cspec
         
         COMPREPLY=()
         cur=${COMP_WORDS[COMP_CWORD]}
     
         if [ $COMP_CWORD = 1 ]; then
     	COMPREPLY=( $( compgen -c $cur ) )
         elif complete -p ${COMP_WORDS[1]} &>/dev/null; then
     	cspec=$( complete -p ${COMP_WORDS[1]} )
     	if [ "${cspec%%-F *}" != "${cspec}" ]; then
     	    # complete -F <function>
     	    #
     	    # COMP_CWORD and COMP_WORDS() are not read-only,
     	    # so we can set them before handing off to regular
     	    # completion routine
     	
     	    # set current token number to 1 less than now
     	    COMP_CWORD=$(( $COMP_CWORD - 1 ))
     	    # get function name
     	    func=${cspec#*-F }
     	    func=${func%% *}
     	    # get current command line minus initial command
     	    cline="${COMP_LINE#$1 }"
     	    # split current command line tokens into array
     		COMP_WORDS=( $cline )
     	    $func $cline
     	elif [ "${cspec#*-[abcdefgjkvu]}" != "" ]; then
     	    # complete -[abcdefgjkvu]
     	    #func=$( echo $cspec | sed -e 's/^.*\(-[abcdefgjkvu]\).*$/\1/' )
     	    func=$( echo $cspec | sed -e 's/^complete//' -e 's/[^ ]*$//' )
     	    COMPREPLY=( $( eval compgen $func $cur ) )
     	elif [ "${cspec#*-A}" != "$cspec" ]; then
     	    # complete -A <type>
     	    func=${cspec#*-A }
     	func=${func%% *}
     	COMPREPLY=( $( compgen -A $func $cur ) )
     	fi
         else
     	COMPREPLY=( $( compgen -f $cur ) )
         fi
     }
     
     
     complete -o default -F _my_command nohup exec eval trace truss strace sotruss gdb
     complete -o default -F _my_command command type which man nice
     
     # Local Variables:
     # mode:shell-script
     # sh-shell:bash
     # End:
    Оставьте свой комментарий !

    Ваше имя:
    Комментарий:
    Оба поля являются обязательными

     Автор  Комментарий к данной статье