11.4 测试与分支

    下面介绍两种在代码块中控制程序流的方法:

    在 shell 脚本中,case 模拟了 C/C++ 语言中的 switch,可以根据条件跳转到其中一个分支。其相当于简写版的 if/then/else 语句。很适合用来创建菜单选项哟!

    样例 11-25. 如何使用 case

    1. #!/bin/bash
    2. # 测试字符的种类。
    3. echo; echo "Hit a key, then hit return."
    4. read Keypress
    5. case "$Keypress" in
    6. [[:lower:]] ) echo "Lowercase letter";;
    7. [[:upper:]] ) echo "Uppercase letter";;
    8. [0-9] ) echo "Digit";;
    9. * ) echo "Punctuation, whitespace, or other";;
    10. esac # 字符范围可以用[方括号]表示,也可以用 POSIX 形式的[[双方括号]]表示。
    11. # 在这个例子的第一个版本中,用来测试是小写还是大写字符使用的是 [a-z] 和 [A-Z]。
    12. # 这在一些特定的语言环境和 Linux 发行版中不起效。
    13. # POSIX 形式具有更好的兼容性。
    14. # 感谢 Frank Wang 指出这一点。
    15. # 练习:
    16. # -----
    17. # 这个脚本接受一个单字符然后结束。
    18. # 修改脚本,使得其可以循环接受输入,并且检测键入的每一个字符,直到键入 "X" 为止。
    19. # 提示:将所有东西包在 "while" 中。
    20. exit 0

    样例 11-26. 使用 case 创建菜单

    1. #!/bin/bash
    2. # 简易的通讯录数据库
    3. clear # 清屏。
    4. echo " Contact List"
    5. echo " ------- ----"
    6. echo "Choose one of the following persons:"
    7. echo
    8. echo "[E]vans, Roland"
    9. echo "[J]ones, Mildred"
    10. echo "[S]mith, Julie"
    11. echo "[Z]ane, Morris"
    12. echo
    13. read person
    14. case "$person" in
    15. # 注意变量是被引用的。
    16. "E" | "e" )
    17. # 同时接受大小写的输入。
    18. echo
    19. echo "Roland Evans"
    20. echo "4321 Flash Dr."
    21. echo "Hardscrabble, CO 80753"
    22. echo "(303) 734-9874"
    23. echo "(303) 734-9892 fax"
    24. echo "revans@zzy.net"
    25. echo "Business partner & old friend"
    26. ;;
    27. # 注意用双分号结束这一个选项。
    28. "J" | "j" )
    29. echo
    30. echo "Mildred Jones"
    31. echo "249 E. 7th St., Apt. 19"
    32. echo "New York, NY 10009"
    33. echo "(212) 533-2814"
    34. echo "(212) 533-9972 fax"
    35. echo "milliej@loisaida.com"
    36. echo "Ex-girlfriend"
    37. echo "Birthday: Feb. 11"
    38. ;;
    39. # Smith 和 Zane 的信息稍后添加。
    40. * )
    41. # 缺省设置。
    42. # 空输入(直接键入回车)也是执行这一部分。
    43. echo
    44. echo "Not yet in database."
    45. ;;
    46. esac
    47. echo
    48. # 练习:
    49. # -----
    50. # 修改脚本,使得其可以循环接受多次输入而不是只显示一个地址后终止脚本。
    51. exit 0

    你可以用 case 来检测命令行参数。

    1. #!/bin/bash
    2. case "$1" in
    3. "") echo "Usage: ${0##*/} <filename>"; exit $E_PARAM;;
    4. # 没有命令行参数,或者第一个参数为空。
    5. # 最后的结果是 $0.
    6. -*) FILENAME=./$1;; # 如果传入的参数以短横线开头,那么将其替换为 ./$1
    7. #+ 以避免后续的命令将其解释为一个选项。
    8. * ) FILENAME=$1;; # 否则赋值为 $1。
    9. esac

    下面是一个更加直观的处理命令行参数的例子:

    样例 11-27. 使用命令替换生成 case 变量

    1. #!/bin/bash
    2. # case-cmd.sh: 使用命令替换生成 "case" 变量。
    3. case $( arch ) in # $( arch ) 返回设备架构。
    4. # 等价于 'uname -m"。
    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. exit 0

    case 还可以用来做字符串模式匹配。

    样例 11-28. 简单的字符串匹配

    1. #!/bin/bash
    2. # match-string.sh: 使用 'case' 结构进行简单的字符串匹配。
    3. match_string ()
    4. { # 字符串精确匹配。
    5. MATCH=0
    6. E_NOMATCH=90
    7. PARAMS=2 # 需要2个参数。
    8. E_BAD_PARAMS=91
    9. [ $# -eq $PARAMS ] || return $E_BAD_PARAMS
    10. case "$1" in
    11. "$2") return $MATCH;;
    12. * ) return $E_NOMATCH;;
    13. esac
    14. }
    15. a=one
    16. b=two
    17. c=three
    18. d=two
    19. match_string $a # 参数个数不够
    20. echo $? # 91
    21. match_string $a $b # 匹配不到
    22. echo $? # 90
    23. match_string $a $d # 匹配成功
    24. echo $? # 0
    25. exit 0
    1. #!/bin/bash
    2. # isaplpha.sh: 使用 "case" 结构检查输入。
    3. SUCCESS=0
    4. FAILURE=1 # 以前是FAILURE=-1,
    5. #+ 但现在 Bash 不允许返回负值。
    6. isalpha () # 测试字符串的第一个字符是否是字母。
    7. {
    8. if [ -z "$1" ] # 检测是否传入参数。
    9. then
    10. return $FAILURE
    11. fi
    12. case "$1" in
    13. [a-zA-Z]*) return $SUCCESS;; # 是否以字母形式开始?
    14. * ) return $FAILURE;;
    15. esac
    16. } # 可以与 C 语言中的函数 "isalpha ()" 作比较。
    17. isalpha2 () # 测试整个字符串是否都是字母。
    18. {
    19. [ $# -eq 1 ] || return $FAILURE
    20. case $1 in
    21. *[!a-zA-Z]*|"") return $FAILURE;;
    22. *) return $SUCCESS;;
    23. esac
    24. }
    25. isdigit () # 测试整个字符串是否都是数字。
    26. { # 换句话说,也就是测试是否是一个整型变量。
    27. [ $# -eq 1 ] || return $FAILURE
    28. case $1 in
    29. *[!0-9]*|"") return $FAILURE;;
    30. *) return $SUCCESS;;
    31. esac
    32. }
    33. check_var () # 包装后的 isalpha ()。
    34. {
    35. if isalpha "$@"
    36. then
    37. echo "\"$*\" begins with an alpha character."
    38. if isalpha2 "$@"
    39. echo "\"$*\" contains only alpha characters."
    40. echo "\"$*\" contains at least one non-alpha character."
    41. fi
    42. else
    43. echo "\"$*\" begins with a non-alpha character."
    44. # 如果没有传入参数同样同样返回“存在非字母”。
    45. fi
    46. echo
    47. }
    48. digit_check () # 包装后的 isdigit ()。
    49. {
    50. if isdigit "$@"
    51. then
    52. echo "\"$*\" contains only digits [0 - 9]."
    53. else
    54. echo "\"$*\" has at least one non-digit character."
    55. fi
    56. echo
    57. }
    58. a=23skidoo
    59. b=H3llo
    60. c=-What?
    61. d=What?
    62. e=$(echo $b) # 命令替换。
    63. f=AbcDef
    64. g=27234
    65. h=27a34
    66. i=27.34
    67. check_var $a
    68. check_var $b
    69. check_var $c
    70. check_var $d
    71. check_var $e
    72. check_var $f
    73. check_var # 如果不传入参数会发送什么?
    74. #
    75. digit_check $g
    76. digit_check $h
    77. digit_check $i
    78. exit 0 # S.C. 改进了本脚本。
    79. # 练习:
    80. # -----
    81. # 写一个函数 'isfloat ()' 来检测输入值是否是浮点数。
    82. # 提示:可以参考函数 'isdigit ()',在其中加入检测合法的小数点即可。

    select

    select 结构是学习自 Korn Shell。其同样可以用来构建菜单。

    而效果则是终端会提示用户输入列表中的一个选项。注意,select 默认使用提示字串3(Prompt String 3,$PS3, 即#?),但同样可以被修改。

    样例 11-30. 使用 select 创建菜单

    1. #!/bin/bash
    2. PS3='Choose your favorite vegetable: ' # 设置提示字串。
    3. # 否则默认为 #?。
    4. echo
    5. select vegetable in "beans" "carrots" "potatoes" "onions" "rutabagas"
    6. do
    7. echo
    8. echo "Your favorite veggie is $vegetable."
    9. echo "Yuck!"
    10. echo
    11. break # 如果没有 'break' 会发生什么?
    12. done
    13. exit
    14. # 练习:
    15. # -----
    16. # 修改脚本,使得其可以接受其他输入而不是 "select" 语句中所指定的。
    17. # 例如,如果用户输入 "peas,",那么脚本会通知用户 "Sorry. That is not on the menu."

    如果 in list 被省略,那么 select 将会使用传入脚本的命令行参数($@)或者传入函数的参数作为 list

    可以与 for variable [in list]in list 被省略的情况做比较。

    样例 11-31. 在函数中使用 select 创建菜单

    1. #!/bin/bash
    2. PS3='Choose your favorite vegetable: '
    3. echo
    4. choice_of()
    5. {
    6. select vegetable
    7. # [in list] 被省略,因此 'select' 将会使用传入函数的参数作为 list。
    8. do
    9. echo
    10. echo "Your favorite veggie is $vegetable."
    11. echo "Yuck!"
    12. echo
    13. break
    14. done
    15. }
    16. choice_of beans rice carrorts radishes rutabaga spinach
    17. # $1 $2 $3 $4 $5 $6
    18. # 传入了函数 choice_of()
    19. exit 0

    还可以参照 。

    1. case $( arch ) in # $( arch ) 返回设备架构。
      ( i386 ) echo 80386-based machine”;;
      # ^ ^
      ( i486 ) echo 80486-based machine”;;
      ( i586 ) echo Pentium-based machine”;;
      ( i686 ) echo Pentium2+-based machine”;;
      ( * ) echo Other type of machine”;;
      esac