10.4. Testing and Branching

The case and select constructs are technically not loops, since they do not iterate the execution of a code block. Like loops, however, they direct program flow according to conditions at the top or bottom of the block.

Controlling program flow in a code block

case (in) / esac

The case construct is the shell equivalent of switch in C/C++. It permits branching to one of a number of code blocks, depending on condition tests. It serves as a kind of shorthand for multiple if/then/else statements and is an appropriate tool for creating menus.

case "$variable" in

á"$condition1" )
ácommand...
á;;

á"$condition2" )
ácommand...
á;;

esac

Note

  • Quoting the variables is not mandatory, since word splitting does not take place.

  • Each test line ends with a right paren ).

  • Each condition block ends with a double semicolon ;;.

  • The entire case block terminates with an esac (case spelled backwards).


Example 10-22. Using case

   1 #!/bin/bash
   2 
   3 echo; echo "Hit a key, then hit return."
   4 read Keypress
   5 
   6 case "$Keypress" in
   7   [a-z]   ) echo "Lowercase letter";;
   8   [A-Z]   ) echo "Uppercase letter";;
   9   [0-9]   ) echo "Digit";;
  10   *       ) echo "Punctuation, whitespace, or other";;
  11 esac  # Allows ranges of characters in [square brackets].
  12 
  13 # Exercise for the reader:
  14 # As the script stands, # it accepts a single keystroke, then terminates.
  15 # Change the script so it accepts continuous input,
  16 # reports on each keystroke, and terminates only when "X" is hit.
  17 # Hint: enclose everything in a "while" loop.
  18 
  19 exit 0


Example 10-23. Creating menus using case

   1 #!/bin/bash
   2 
   3 # Crude address database
   4 
   5 clear # Clear the screen.
   6 
   7 echo "          Contact List"
   8 echo "          ------- ----"
   9 echo "Choose one of the following persons:" 
  10 echo
  11 echo "[E]vans, Roland"
  12 echo "[J]ones, Mildred"
  13 echo "[S]mith, Julie"
  14 echo "[Z]ane, Morris"
  15 echo
  16 
  17 read person
  18 
  19 case "$person" in
  20 # Note variable is quoted.
  21 
  22   "E" | "e" )
  23   # Accept upper or lowercase input.
  24   echo
  25   echo "Roland Evans"
  26   echo "4321 Floppy Dr."
  27   echo "Hardscrabble, CO 80753"
  28   echo "(303) 734-9874"
  29   echo "(303) 734-9892 fax"
  30   echo "revans@zzy.net"
  31   echo "Business partner & old friend"
  32   ;;
  33 # Note double semicolon to terminate
  34 # each option.
  35 
  36   "J" | "j" )
  37   echo
  38   echo "Mildred Jones"
  39   echo "249 E. 7th St., Apt. 19"
  40   echo "New York, NY 10009"
  41   echo "(212) 533-2814"
  42   echo "(212) 533-9972 fax"
  43   echo "milliej@loisaida.com"
  44   echo "Girlfriend"
  45   echo "Birthday: Feb. 11"
  46   ;;
  47 
  48 # Add info for Smith & Zane later.
  49 
  50           * )
  51    # Default option.	  
  52    # Empty input (hitting RETURN) fits here, too.
  53    echo
  54    echo "Not yet in database."
  55   ;;
  56 
  57 esac
  58 
  59 echo
  60 
  61 # Exercise for the reader:
  62 # Change the script so it accepts continuous input,
  63 # instead of terminating after displaying just one address.
  64 
  65 exit 0

An exceptionally clever use of case involves testing for command-line parameters.
   1 #! /bin/bash
   2 
   3 case "$1" in
   4 "") echo "Usage: ${0##*/} <filename>"; exit 65;;  # No command-line parameters,
   5                                                   # or first parameter empty.
   6 # Note that ${0##*/} is ${var##pattern} param substitution. Net result is $0.
   7 
   8 -*) FILENAME=./$1;;   # If filename passed as argument ($1) starts with a dash,
   9                       # replace it with ./$1
  10                       # so further commands don't interpret it as an option.
  11 
  12 * ) FILENAME=$1;;     # Otherwise, $1.
  13 esac


Example 10-24. Using command substitution to generate the case variable

   1 #!/bin/bash
   2 # Using command substitution to generate a "case" variable.
   3 
   4 case $( arch ) in   # "arch" returns machine architecture.
   5 i386 ) echo "80386-based machine";;
   6 i486 ) echo "80486-based machine";;
   7 i586 ) echo "Pentium-based machine";;
   8 i686 ) echo "Pentium2+-based machine";;
   9 *    ) echo "Other type of machine";;
  10 esac
  11 
  12 exit 0

A case construct can filter strings for globbing patterns.


Example 10-25. Simple string matching

   1 #!/bin/bash
   2 # match-string.sh: simple string matching
   3 
   4 match_string ()
   5 {
   6   MATCH=0
   7   NOMATCH=90
   8   PARAMS=2     # Function requires 2 arguments.
   9   BAD_PARAMS=91
  10 
  11   [ $# -eq $PARAMS ] || return $BAD_PARAMS
  12 
  13   case "$1" in
  14   "$2") return $MATCH;;
  15   *   ) return $NOMATCH;;
  16   esac
  17 
  18 }  
  19 
  20 
  21 a=one
  22 b=two
  23 c=three
  24 d=two
  25 
  26 
  27 match_string $a     # wrong number of parameters
  28 echo $?             # 91
  29 
  30 match_string $a $b  # no match
  31 echo $?             # 90
  32 
  33 match_string $b $d  # match
  34 echo $?             # 0
  35 
  36 
  37 exit 0		    


Example 10-26. Checking for alphabetic input

   1 #!/bin/bash
   2 # Using "case" structure to filter a string.
   3 
   4 SUCCESS=0
   5 FAILURE=-1
   6 
   7 isalpha ()  # Tests whether *first character* of input string is alphabetic.
   8 {
   9 if [ -z "$1" ]                # No argument passed?
  10 then
  11   return $FAILURE
  12 fi
  13 
  14 case "$1" in
  15 [a-zA-Z]*) return $SUCCESS;;  # Begins with a letter?
  16 *        ) return $FAILURE;;
  17 esac
  18 }             # Compare this with "isalpha ()" function in C.
  19 
  20 
  21 isalpha2 ()   # Tests whether *entire string* is alphabetic.
  22 {
  23   [ $# -eq 1 ] || return $FAILURE
  24 
  25   case $1 in
  26   *[!a-zA-Z]*|"") return $FAILURE;;
  27                *) return $SUCCESS;;
  28   esac
  29 }
  30 
  31 
  32 
  33 check_var ()  # Front-end to isalpha().
  34 {
  35 if isalpha "$@"
  36 then
  37   echo "$* = alpha"
  38 else
  39   echo "$* = non-alpha"  # Also "non-alpha" if no argument passed.
  40 fi
  41 }
  42 
  43 a=23skidoo
  44 b=H3llo
  45 c=-What?
  46 d=`echo $b`   # Command substitution.
  47 
  48 check_var $a
  49 check_var $b
  50 check_var $c
  51 check_var $d
  52 check_var     # No argument passed, so what happens?
  53 
  54 
  55 # Script improved by S.C.
  56 
  57 exit 0

select

The select construct, adopted from the Korn Shell, is yet another tool for building menus.

select variable [in list]
do
ácommand...
ábreak
done

This prompts the user to enter one of the choices presented in the variable list. Note that select uses the PS3 prompt (#? ) by default, but that this may be changed.


Example 10-27. Creating menus using select

   1 #!/bin/bash
   2 
   3 PS3='Choose your favorite vegetable: ' # Sets the prompt string.
   4 
   5 echo
   6 
   7 select vegetable in "beans" "carrots" "potatoes" "onions" "rutabagas"
   8 do
   9   echo
  10   echo "Your favorite veggie is $vegetable."
  11   echo "Yuck!"
  12   echo
  13   break  # if no 'break' here, keeps looping forever.
  14 done
  15 
  16 exit 0

If in list is omitted, then select uses the list of command line arguments ($@) passed to the script or to the function in which the select construct is embedded.

Compare this to the behavior of a

for variable [in list]

construct with the in list omitted.


Example 10-28. Creating menus using select in a function

   1 #!/bin/bash
   2 
   3 PS3='Choose your favorite vegetable: '
   4 
   5 echo
   6 
   7 choice_of()
   8 {
   9 select vegetable
  10 # [in list] omitted, so 'select' uses arguments passed to function.
  11 do
  12   echo
  13   echo "Your favorite veggie is $vegetable."
  14   echo "Yuck!"
  15   echo
  16   break
  17 done
  18 }
  19 
  20 choice_of beans rice carrots radishes tomatoes spinach
  21 #         $1    $2   $3      $4       $5       $6
  22 #         passed to choice_of() function
  23 
  24 exit 0