Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
---|---|---|
Prev | Chapter 11. 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 scripting analog to 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 11-25. 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 # POSIX is more portable. 20 # Thanks to Frank Wang for pointing this out. 21 22 # Exercise: 23 # -------- 24 # As the script stands, it accepts a single keystroke, then terminates. 25 # Change the script so it accepts repeated input, 26 #+ reports on each keystroke, and terminates only when "X" is hit. 27 # Hint: enclose everything in a "while" loop. 28 29 exit 0 |
Example 11-26. 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 Flash 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 "Ex-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 multiple inputs, 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 $E_PARAM;; 5 # No command-line parameters, 6 # or first parameter empty. 7 # Note that ${0##*/} is ${var##pattern} param substitution. 8 # Net result is $0. 9 10 -*) FILENAME=./$1;; # If filename passed as argument ($1) 11 #+ starts with a dash, 12 #+ replace it with ./$1 13 #+ so further commands don't interpret it 14 #+ as an option. 15 16 * ) FILENAME=$1;; # Otherwise, $1. 17 esac |
Here is a more straightforward example of command-line parameter handling:
1 #! /bin/bash 2 3 4 while [ $# -gt 0 ]; do # Until you run out of parameters . . . 5 case "$1" in 6 -d|--debug) 7 # "-d" or "--debug" parameter? 8 DEBUG=1 9 ;; 10 -c|--conf) 11 CONFFILE="$2" 12 shift 13 if [ ! -f $CONFFILE ]; then 14 echo "Error: Supplied file doesn't exist!" 15 exit $E_CONFFILE # File not found error. 16 fi 17 ;; 18 esac 19 shift # Check next set of parameters. 20 done 21 22 # From Stefano Falsetto's "Log2Rot" script, 23 #+ part of his "rottlog" package. 24 # Used with permission. |
Example 11-27. Using command substitution to generate the case variable
1 #!/bin/bash 2 # case-cmd.sh: Using command substitution to generate a "case" variable. 3 4 case $( arch ) in # $( arch ) returns machine architecture. 5 # Equivalent to 'uname -m' ... 6 i386 ) echo "80386-based machine";; 7 i486 ) echo "80486-based machine";; 8 i586 ) echo "Pentium-based machine";; 9 i686 ) echo "Pentium2+-based machine";; 10 * ) echo "Other type of machine";; 11 esac 12 13 exit 0 |
A case construct can filter strings for globbing patterns.
Example 11-28. Simple string matching
1 #!/bin/bash 2 # match-string.sh: Simple string matching 3 # using a 'case' construct. 4 5 match_string () 6 { # Exact string match. 7 MATCH=0 8 E_NOMATCH=90 9 PARAMS=2 # Function requires 2 arguments. 10 E_BAD_PARAMS=91 11 12 [ $# -eq $PARAMS ] || return $E_BAD_PARAMS 13 14 case "$1" in 15 "$2") return $MATCH;; 16 * ) return $E_NOMATCH;; 17 esac 18 19 } 20 21 22 a=one 23 b=two 24 c=three 25 d=two 26 27 28 match_string $a # wrong number of parameters 29 echo $? # 91 30 31 match_string $a $b # no match 32 echo $? # 90 33 34 match_string $b $d # match 35 echo $? # 0 36 37 38 exit 0 |
Example 11-29. 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 # Was FAILURE=-1, 6 #+ but Bash no longer allows negative return value. 7 8 isalpha () # Tests whether *first character* of input string is alphabetic. 9 { 10 if [ -z "$1" ] # No argument passed? 11 then 12 return $FAILURE 13 fi 14 15 case "$1" in 16 [a-zA-Z]*) return $SUCCESS;; # Begins with a letter? 17 * ) return $FAILURE;; 18 esac 19 } # Compare this with "isalpha ()" function in C. 20 21 22 isalpha2 () # Tests whether *entire string* is alphabetic. 23 { 24 [ $# -eq 1 ] || return $FAILURE 25 26 case $1 in 27 *[!a-zA-Z]*|"") return $FAILURE;; 28 *) return $SUCCESS;; 29 esac 30 } 31 32 isdigit () # Tests whether *entire string* is numerical. 33 { # In other words, tests for integer variable. 34 [ $# -eq 1 ] || return $FAILURE 35 36 case $1 in 37 *[!0-9]*|"") return $FAILURE;; 38 *) return $SUCCESS;; 39 esac 40 } 41 42 43 44 check_var () # Front-end to isalpha (). 45 { 46 if isalpha "$@" 47 then 48 echo "\"$*\" begins with an alpha character." 49 if isalpha2 "$@" 50 then # No point in testing if first char is non-alpha. 51 echo "\"$*\" contains only alpha characters." 52 else 53 echo "\"$*\" contains at least one non-alpha character." 54 fi 55 else 56 echo "\"$*\" begins with a non-alpha character." 57 # Also "non-alpha" if no argument passed. 58 fi 59 60 echo 61 62 } 63 64 digit_check () # Front-end to isdigit (). 65 { 66 if isdigit "$@" 67 then 68 echo "\"$*\" contains only digits [0 - 9]." 69 else 70 echo "\"$*\" has at least one non-digit character." 71 fi 72 73 echo 74 75 } 76 77 a=23skidoo 78 b=H3llo 79 c=-What? 80 d=What? 81 e=$(echo $b) # Command substitution. 82 f=AbcDef 83 g=27234 84 h=27a34 85 i=27.34 86 87 check_var $a 88 check_var $b 89 check_var $c 90 check_var $d 91 check_var $e 92 check_var $f 93 check_var # No argument passed, so what happens? 94 # 95 digit_check $g 96 digit_check $h 97 digit_check $i 98 99 100 exit 0 # Script improved by S.C. 101 102 # Exercise: 103 # -------- 104 # Write an 'isfloat ()' function that tests for floating point numbers. 105 # Hint: The function duplicates 'isdigit ()', 106 #+ 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 this may be changed.
Example 11-30. Creating menus using select
1 #!/bin/bash 2 3 PS3='Choose your favorite vegetable: ' # Sets the prompt string. 4 # Otherwise it defaults to #? . 5 6 echo 7 8 select vegetable in "beans" "carrots" "potatoes" "onions" "rutabagas" 9 do 10 echo 11 echo "Your favorite veggie is $vegetable." 12 echo "Yuck!" 13 echo 14 break # What happens if there is no 'break' here? 15 done 16 17 exit 18 19 # Exercise: 20 # -------- 21 # Fix this script to accept user input not specified in 22 #+ the "select" statement. 23 # For example, if the user inputs "peas," 24 #+ the script would respond "Sorry. That is not on the menu." |
If in list is omitted, then select uses the list of command line arguments ($@) passed to the script or the function containing the select construct.
Compare this to the behavior of a
for variable [in list]
construct with the in list omitted.Example 11-31. 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 rutabaga spinach 21 # $1 $2 $3 $4 $5 $6 22 # passed to choice_of() function 23 24 exit 0 |
See also Example 37-3.
[1] | Pattern-match lines may also start with a ( left paren to give the layout a more structured appearance.
|