| Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
|---|---|---|
| Prev | Chapter 10. Loops and Branches | Next | 
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
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 
|  | 
 
 | 
Example 10-24. Using case
| 1 #!/bin/bash 2 # Testing ranges of characters. 3 4 echo; echo "Hit a key, then hit return." 5 read Keypress 6 7 case "$Keypress" in 8 [[:lower:]] ) echo "Lowercase letter";; 9 [[:upper:]] ) echo "Uppercase letter";; 10 [0-9] ) echo "Digit";; 11 * ) echo "Punctuation, whitespace, or other";; 12 esac # Allows ranges of characters in [square brackets], 13 #+ or POSIX ranges in [[double square brackets. 14 15 # In the first version of this example, 16 #+ the tests for lowercase and uppercase characters were 17 #+ [a-z] and [A-Z]. 18 # This no longer works in certain locales and/or Linux distros. 19 # Thanks to Frank Wang for pointing this out. 20 21 # Exercise: 22 # -------- 23 # As the script stands, it accepts a single keystroke, then terminates. 24 # Change the script so it accepts continuous input, 25 #+ reports on each keystroke, and terminates only when "X" is hit. 26 # Hint: enclose everything in a "while" loop. 27 28 exit 0 | 
Example 10-25. 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 each option. 34 35 "J" | "j" ) 36 echo 37 echo "Mildred Jones" 38 echo "249 E. 7th St., Apt. 19" 39 echo "New York, NY 10009" 40 echo "(212) 533-2814" 41 echo "(212) 533-9972 fax" 42 echo "milliej@loisaida.com" 43 echo "Girlfriend" 44 echo "Birthday: Feb. 11" 45 ;; 46 47 # Add info for Smith & Zane later. 48 49 * ) 50 # Default option. 51 # Empty input (hitting RETURN) fits here, too. 52 echo 53 echo "Not yet in database." 54 ;; 55 56 esac 57 58 echo 59 60 # Exercise: 61 # -------- 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-26. 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-27. 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-28. Checking for alphabetic input
|    1 #!/bin/bash
   2 # isalpha.sh: Using a "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 isdigit ()    # Tests whether *entire string* is numerical.
  32 {             # In other words, tests for integer variable.
  33   [ $# -eq 1 ] || return $FAILURE
  34 
  35   case $1 in
  36   *[!0-9]*|"") return $FAILURE;;
  37             *) return $SUCCESS;;
  38   esac
  39 }
  40 
  41 
  42 
  43 check_var ()  # Front-end to isalpha ().
  44 {
  45 if isalpha "$@"
  46 then
  47   echo "\"$*\" begins with an alpha character."
  48   if isalpha2 "$@"
  49   then        # No point in testing if first char is non-alpha.
  50     echo "\"$*\" contains only alpha characters."
  51   else
  52     echo "\"$*\" contains at least one non-alpha character."
  53   fi  
  54 else
  55   echo "\"$*\" begins with a non-alpha character."
  56               # Also "non-alpha" if no argument passed.
  57 fi
  58 
  59 echo
  60 
  61 }
  62 
  63 digit_check ()  # Front-end to isdigit ().
  64 {
  65 if isdigit "$@"
  66 then
  67   echo "\"$*\" contains only digits [0 - 9]."
  68 else
  69   echo "\"$*\" has at least one non-digit character."
  70 fi
  71 
  72 echo
  73 
  74 }
  75 
  76 a=23skidoo
  77 b=H3llo
  78 c=-What?
  79 d=What?
  80 e=`echo $b`   # Command substitution.
  81 f=AbcDef
  82 g=27234
  83 h=27a34
  84 i=27.34
  85 
  86 check_var $a
  87 check_var $b
  88 check_var $c
  89 check_var $d
  90 check_var $e
  91 check_var $f
  92 check_var     # No argument passed, so what happens?
  93 #
  94 digit_check $g
  95 digit_check $h
  96 digit_check $i
  97 
  98 
  99 exit 0        # Script improved by S.C.
 100 
 101 # Exercise:
 102 # --------
 103 #  Write an 'isfloat ()' function that tests for floating point numbers.
 104 #  Hint: The function duplicates 'isdigit ()',
 105 #+ but adds a test for a mandatory decimal point. | 
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-29. 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 # What happens if there is no 'break' here? 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-30. 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 | 
See also Example 35-3.