Chinaunix首页 | 论坛 | 博客
  • 博客访问: 512524
  • 博文数量: 119
  • 博客积分: 5054
  • 博客等级: 大校
  • 技术积分: 1305
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-03 13:13
文章分类

全部博文(119)

文章存档

2011年(4)

2010年(115)

我的朋友

分类:

2010-01-03 22:37:09

摘自《高级bash脚本编程指南》

对代码块的操作是构造和组织shell脚本的关键. 循环和分支结构为脚本编程提供了操作代码块的工具.

循环

循环就是迭代(重复)一些命令的代码块, 如果循环控制条件不满足的话, 就结束循环.

for循环

for arg in [list]

这是一个基本的循环结构. 它与C语言中的for循环结构有很大的不同.

    for arg in [list]
    do
    command(s)...
    done

在循环的每次执行中, arg将顺序的访问list中列出的变量.

  1 for arg in "$var1" "$var2" "$var3" ... "$varN"
  2 # 在第1次循环中, arg = $var1
  3 # 在第2次循环中, arg = $var2
  4 # 在第3次循环中, arg = $var3
  5 # ...
  6 # 在第N此循环中, arg = $varN
  7
  8 # 在[list]中的参数加上双引号是为了阻止单词分割.

list中的参数允许包含通配符.

如果do和for想在同一行中出现, 那么在它们之间需要添加一个分号.

for arg in [list] ; do 

例子 1. 一个简单的for循环

  1 #!/bin/bash
  2 # 列出所有的行星名称. (译者注: 现在的太阳系行星已经有了变化^_^)
  3
  4 for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
  5 do
  6   echo $planet  # 每个行星都被单独打印在一行上.
  7 done
  8
  9 echo
 10
 11 for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
 12 # 所有的行星名称都打印在同一行上.
 13 # 整个'list'都被双引号封成了一个变量.
 14 do
 15   echo $planet
 16 done
 17
 18 exit 0

每个[list]中的元素都可能包含多个参数. 在处理参数组时, 这是非常有用的. 在这种情况下, 使用set命令来强制解析(单词分割)每个[list]中的元素, 并且将每个解析出来的部分都分配到一个位置参数中.

例子 2. 每个[list]元素中都带有两个参数的for循环

  1 #!/bin/bash
  2 # 还是行星.
  3
  4 # 用行星距太阳的距离来分配行星的名字.
  5
  6 for planet in "Mercury 36" "Venus 67" "Earth 93"  "Mars 142" "Jupiter 483"
  7 do
  8   set -- $planet  # 解析变量"planet"并且设置位置参数.
  9   # "--" 将防止$planet为空, 或者是以一个破折号开头.
 10
 11   # 可能需要保存原始的位置参数, 因为它们被覆盖了.
 12   # 一种方法就是使用数组.
 13   #        original_params=("$@")
 14
 15   echo "$1		$2,000,000 miles from the sun"
 16   #-------two  tabs---把后边的0和2连接起来
 17 done
 18
 19 # (感谢, S.C., 对此问题进行的澄清.)
 20
 21 exit 0

可以将一个变量放在for循环的[list]位置上.

例子 3. 文件信息: 对包含在变量中的文件列表进行操作

  1 #!/bin/bash
  2 # fileinfo.sh
  3
  4 FILES="/usr/sbin/accept
  5 /usr/sbin/pwck
  6 /usr/sbin/chroot
  7 /usr/bin/fakefile
  8 /sbin/badblocks
  9 /sbin/ypbind"     # 这是你所关心的文件列表.
 10                   # 扔进去一个假文件, /usr/bin/fakefile.
 11
 12 echo
 13
 14 for file in $FILES
 15 do
 16
 17   if [ ! -e "$file" ]       # 检查文件是否存在.
 18   then
 19     echo "$file does not exist."; echo
 20     continue                # 继续下一个.
 21    fi
 22
 23   ls -l $file | awk '{ print $9 "         file size: " $5 }'  # 打印两个域.
 24   whatis `basename $file`   # 文件信息.
 25   # 注意whatis数据库需要提前建立好.
 26   # 要想达到这个目的, 以root身份运行/usr/bin/makewhatis.
 27   echo
 28 done
 29
 30 exit 0

如果在for循环的[list]中有通配符 (*和?), 那么将会发生通配(globbing), 也就是文件名扩展.

例子 4. 在for循环中操作文件

  1 #!/bin/bash
  2 # list-glob.sh: 使用"globbing", 在for循环中产生[list]
  3
  4 echo
  5
  6 for file in *
  7 #           ^  在表达式中识别文件名匹配时,
  8 #+             Bash将执行文件名扩展.
  9 do
 10   ls -l "$file"  # 列出在$PWD(当前目录)中的所有文件.
 11   #  回想一下,通配符"*"能够匹配所有文件,
 12   #+ 然而,在"globbing"中,是不能比配"."文件的.
 13
 14   #  如果没匹配到任何文件,那它将扩展成自己.
 15   #  为了不让这种情况发生,那就设置nullglob选项
 16   #+   (shopt -s nullglob).
 17   #  感谢, S.C.
 18 done
 19
 20 echo; echo
 21
 22 for file in [jx]*
 23 do
 24   rm -f $file    # 只删除当前目录下以"j"或"x"开头的文件.
 25   echo "Removed file \"$file\"".
 26 done
 27
 28 echo
 29
 30 exit 0

在一个for循环中忽略in [list]部分的话, 将会使循环操作$@ — 从命令行传递给脚本的位置参数.

例子 5. 在for循环中省略 in [list] 部分

  1 #!/bin/bash
  2
  3 #  使用两种方式来调用这个脚本, 一种带参数, 另一种不带参数,
  4 #+ 并观察在这两种情况下, 此脚本的行为.
  5
  6 for a
  7 do
  8  echo -n "$a "
  9 done
 10
 11 #  省略'in list'部分, 因此循环将会操作'$@'
 12 #+ (包括空白的命令行参数列表).
 13
 14 echo
 15
 16 exit 0

也可以使用命令替换 来产生for循环的[list].

例子 6. 使用命令替换来产生for循环的[list]

  1 #!/bin/bash
  2 #  for-loopcmd.sh: 带[list]的for循环,
  3 #+ [list]是由命令替换所产生的.
  4
  5 NUMBERS="9 7 3 8 37.53"
  6
  7 for number in `echo $NUMBERS`  # for number in 9 7 3 8 37.53
  8 do
  9   echo -n "$number "
 10 done
 11
 12 echo
 13 exit 0

例子 7. 列出系统上的所有用户

  1 #!/bin/bash
  2 # userlist.sh
  3
  4 PASSWORD_FILE=/etc/passwd
  5 n=1           # User number
  6
  7 for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" )
  8 # 域分隔 = :             ^^^^^^
  9 # 打印出第一个域                 ^^^^^^^^
 10 # 从password文件中取得输入                     ^^^^^^^^^^^^^^^^^
 11 do
 12   echo "USER #$n = $name"
 13   let "n += 1"
 14 done
 15
 16
 17 # USER #1 = root
 18 # USER #2 = bin
 19 # USER #3 = daemon
 20 # ...
 21 # USER #30 = bozo
 22
 23 exit 0
 24
 25 #  练习:
 26 #  -----
 27 #  一个普通用户(或者是一个普通用户运行的脚本)
 28 #+ 怎么才能够读取/etc/passwd呢?
 29 #  这是否是一个安全漏洞? 为什么是?为什么不是?

for循环的输出也可以通过管道传递到一个或多个命令中.

例子 8. 列出目录中所有的符号链接

  1 #!/bin/bash
  2 # symlinks.sh: 列出目录中所有的符号链接文件.
  3
  4
  5 directory=${1-`pwd`}
  6 #  如果没有其他特殊的指定,
  7 #+ 默认为当前工作目录.
  8 #  下边的代码块, 和上边这句等价.
  9 # ----------------------------------------------------------
 10 # ARGS=1                 # 需要一个命令行参数.
 11 #
 12 # if [ $# -ne "$ARGS" ]  # 如果不是单个参数的话...
 13 # then
 14 #   directory=`pwd`      # 当前工作目录
 15 # else
 16 #   directory=$1
 17 # fi
 18 # ----------------------------------------------------------
 19
 20 echo "symbolic links in directory \"$directory\""
 21
 22 for file in "$( find $directory -type l )"   # -type l = 符号链接
 23 do
 24   echo "$file"
 25 done | sort                                  # 否则的话, 列出的文件都是未经排序的.
 26 #  严格意义上说, 这里并不一定非要一个循环不可.
 27 #+ 因为"find"命令的输出将被扩展成一个单词.
 28 #  然而, 这种方式很容易理解也很容易说明.
 29
 30 #  就像Dominik 'Aeneas' Schnitzer所指出的,
 31 #+ 如果没将$( find $directory -type l )用""引用起来的话,
 32 #+ 那么将会把一个带有空白部分的文件名拆分成以空白分隔的两部分(文件名允许有空白).
 33 #  即使这里只会取出每个参数的第一个域.
 34
 35 exit 0
 36
 37
 38 # Jean Helou建议采用下边的方法:
 39
 40 echo "symbolic links in directory \"$directory\""
 41 # 当前IFS的备份. 要小心使用这个值.
 42 OLDIFS=$IFS
 43 IFS=:
 44
 45 for file in $(find $directory -type l -printf "%p$IFS")
 46 do     #                              ^^^^^^^^^^^^^^^^
 47        echo "$file"
 48        done|sort

循环的stdout可以重定向到文件中, 我们对上边的例子做了一点修改.

例子 9. 将目录中所有符号链接文件的名字保存到一个文件中

  1 #!/bin/bash
  2 # symlinks.sh: 列出目录中所有的符号链接文件.
  3
  4 OUTFILE=symlinks.list                         # 保存符号链接文件名的文件
  5
  6 directory=${1-`pwd`}
  7 #  如果没有其他特殊的指定,
  8 #+ 默认为当前工作目录.
  9
 10
 11 echo "symbolic links in directory \"$directory\"" > "$OUTFILE"
 12 echo "---------------------------" >> "$OUTFILE"
 13
 14 for file in "$( find $directory -type l )"    # -type l = 符号链接
 15 do
 16   echo "$file"
 17 done | sort >> "$OUTFILE"                     # 循环的stdout
 18 #           ^^^^^^^^^^^^^                       重定向到一个文件中.
 19
 20 exit 0

有一种非常像C语言for循环的语法形式. 需要使用(()).

例子 10. 一个C风格的for循环

  1 #!/bin/bash
  2 # 两种循环到10的方法.
  3
  4 echo
  5
  6 # 标准语法.
  7 for a in 1 2 3 4 5 6 7 8 9 10
  8 do
  9   echo -n "$a "
 10 done
 11
 12 echo; echo
 13
 14 # +==========================================+
 15
 16 # 现在, 让我们用C风格语法来做相同的事情.
 17
 18 LIMIT=10
 19
 20 for ((a=1; a <= LIMIT ; a++))  # 双圆括号, 并且"LIMIT"变量前面没有"$".
 21 do
 22   echo -n "$a "
 23 done                           # 这是一个借用'ksh93'的结构.
 24
 25 echo; echo
 26
 27 # +=========================================================================+
 28
 29 # 让我们使用C语言的"逗号操作符", 来同时增加两个变量的值.
 30
 31 for ((a=1, b=1; a <= LIMIT ; a++, b++))  # 逗号将同时进行两条操作.
 32 do
 33   echo -n "$a-$b "
 34 done
 35
 36 echo; echo
 37
 38 exit 0

while

这种结构在循环的开头判断条件是否满足, 如果条件一直满足, 那么就一直循环下去 (返回0作为退出状态码). 与for循环的区别是, while循环更适合在循环次数未知的情况下使用.

    while [condition]
    do
    command...
    done

与for循环一样, 如果想把do和条件判断放到同一行上的话, 还是需要一个分号.

   while [condition] ; do 

例子 11. while循环

  1 #!/bin/bash
  2
  3 echo
  4                                # 等价于:
  5 while [ "$var1" != "end" ]     # while test "$var1" != "end"
  6 do
  7   echo "Input variable #1 (end to exit) "
  8   read var1                    # 为什么不使用'read $var1'?
  9   echo "variable #1 = $var1"   # 因为包含"#", 所以需要""
 10   # 如果输入为'end', 那么就在这里echo.
 11   # 不在这里判断结束, 在循环顶判断.
 12   echo
 13 done
 14
 15 exit 0

一个while循环可以有多个判断条件. 但是只有最后一个才能够决定是否能够退出循环. 然而这里需要一种有点特殊的循环语法.

例子 12. 多条件的while循环

  1 #!/bin/bash
  2
  3 var1=unset
  4 previous=$var1
  5
  6 while echo "previous-variable = $previous"
  7       echo
  8       previous=$var1
  9       [ "$var1" != end ] # 纪录之前的$var1.
 10       # 这个"while"中有4个条件, 但是只有最后一个能够控制循环.
 11       # *最后*的退出状态就是由这最后一个条件来决定.
 12 do
 13 echo "Input variable #1 (end to exit) "
 14   read var1
 15   echo "variable #1 = $var1"
 16 done
 17
 18 # 尝试理解这个脚本的运行过程.
 19 # 这里还是有点小技巧的.
 20
 21 exit 0

与for循环一样, while循环也可以通过(())来使用C风格的语法.

例子 13. C风格的while循环

  1 #!/bin/bash
  2 # wh-loopc.sh: 循环10次的"while"循环.
  3
  4 LIMIT=10
  5 a=1
  6
  7 while [ "$a" -le $LIMIT ]
  8 do
  9   echo -n "$a "
 10   let "a+=1"
 11 done           # 到目前为止都没有什么令人惊奇的地方.
 12
 13 echo; echo
 14
 15 # +=================================================================+
 16
 17 # 现在, 重复C风格的语法.
 18
 19 ((a = 1))      # a=1
 20 # 双圆括号允许赋值两边的空格, 就像C语言一样.
 21
 22 while (( a <= LIMIT ))   # 双圆括号, 变量前边没有"$".
 23 do
 24   echo -n "$a "
 25   ((a += 1))   # let "a+=1"
 26   # Yes, 看到了吧.
 27   # 双圆括号允许像C风格的语法一样增加变量的值.
 28 done
 29
 30 echo
 31
 32 # 现在, C程序员可以在Bash中找到回家的感觉了吧.
 33
 34 exit 0

while循环的stdin可以使用<来重定向到一个文件.while循环的stdin支持管道.

until

这个结构在循环的顶部判断条件, 并且如果条件一直为false, 那么就一直循环下去. (与while循环相反).

    until [condition-is-true]
    do
    command...
    done

注意, until循环的条件判断在循环的顶部, 这与某些编程语言是不同的.

与for循环一样, 如果想把do和条件判断放在同一行里, 那么就需要使用分号.

    until [condition-is-true] ; do 

例子 14. until循环

  1 #!/bin/bash
  2
  3 END_CONDITION=end
  4
  5 until [ "$var1" = "$END_CONDITION" ]
  6 # 在循环的顶部进行条件判断.
  7 do
  8   echo "Input variable #1 "
  9   echo "($END_CONDITION to exit)"
 10   read var1
 11   echo "variable #1 = $var1"
 12   echo
 13 done
 14
 15 exit 0

嵌套循环

嵌套循环就是在一个循环中还有一个循环, 内部循环在外部循环体中. 在外部循环的每次执行过程中都会触发内部循环, 直到内部循环执行结束. 外部循环执行了多少次, 内部循环就完成多少次. 当然, 无论是内部循环还是外部循环的break语句都会打断处理过程.

例子 15. 嵌套循环

  1 #!/bin/bash
  2 # nested-loop.sh: 嵌套的"for"循环.
  3
  4 outer=1             # 设置外部循环计数.
  5
  6 # 开始外部循环.
  7 for a in 1 2 3 4 5
  8 do
  9   echo "Pass $outer in outer loop."
 10   echo "---------------------"
 11   inner=1           # 重置内部循环计数.
 12
 13   # ===============================================
 14   # 开始内部循环.
 15   for b in 1 2 3 4 5
 16   do
 17     echo "Pass $inner in inner loop."
 18     let "inner+=1"  # 增加内部循环计数.
 19   done
 20   # 内部循环结束.
 21   # ===============================================
 22
 23   let "outer+=1"    # 增加外部循环的计数.
 24   echo              # 每次外部循环之间的间隔.
 25 done
 26 # 外部循环结束.
 27
 28 exit 0

循环控制

影响循环行为的命令

break, continue

break和continue这两个循环控制命令(这两个命令是shell的内建命令, 而不象其他的循环命令那样, 比如while和case, 这两个是关键字.)与其他语言的类似命令的行为是相同的. break命令用来跳出循环, 而continue命令只会跳过本次循环, 忽略本次循环剩余的代码, 进入循环的下一次迭代.

例子 16. break和continue命令在循环中的效果

      1 #!/bin/bash
      2
      3 LIMIT=19  # 上限
      4
      5 echo
      6 echo "Printing Numbers 1 through 20 (but not 3 and 11)."
      7
      8 a=0
      9
     10 while [ $a -le "$LIMIT" ]
     11 do
     12  a=$(($a+1))
     13
     14  if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  # 除了3和11.
     15  then
     16    continue      # 跳过本次循环剩余的语句.
     17  fi
     18
     19  echo -n "$a "   # 在$a等于3和11的时候,这句将不会执行.
     20 done
     21
     22 # 练习:
     23 # 为什么循环会打印出20?
     24
     25 echo; echo
     26
     27 echo Printing Numbers 1 through 20, but something happens after 2.
     28
     29 ##################################################################
     30
     31 # 同样的循环, 但是用'break'来代替'continue'.
     32
     33 a=0
     34
     35 while [ "$a" -le "$LIMIT" ]
     36 do
     37  a=$(($a+1))
     38
     39  if [ "$a" -gt 2 ]
     40  then
     41    break  # 将会跳出整个循环.
     42  fi
     43
     44  echo -n "$a "
     45 done
     46
     47 echo; echo; echo
     48
     49 exit 0

break命令可以带一个参数. 一个不带参数的break命令只能退出最内层的循环, 而break N可以退出N层循环.

例子 17. 多层循环的退出

      1 #!/bin/bash
      2 # break-levels.sh: 退出循环.
      3
      4 # "break N" 退出N层循环.
      5
      6 for outerloop in 1 2 3 4 5
      7 do
      8   echo -n "Group $outerloop:   "
      9
     10   # --------------------------------------------------------
     11   for innerloop in 1 2 3 4 5
     12   do
     13     echo -n "$innerloop "
     14
     15     if [ "$innerloop" -eq 3 ]
     16     then
     17       break  # 试试   break 2   来看看发生什么事.
     18              # (内部循环和外部循环都被"Break"了. )
     19     fi
     20   done
     21   # --------------------------------------------------------
     22
     23   echo
     24 done
     25
     26 echo
     27
     28 exit 0

continue命令也可以象break命令一样带一个参数. 一个不带参数的continue命令只会去掉本次循环的剩余代码. 而continue N将会把N层循环的剩余代码都去掉, 但是循环的次数不变.

例子 18. 多层循环的continue

      1 #!/bin/bash
      2 # "continue N" 命令, 将让N层的循环全部被continue.
      3
      4 for outer in I II III IV V           # 外部循环
      5 do
      6   echo; echo -n "Group $outer: "
      7
      8   # --------------------------------------------------------------------
      9   for inner in 1 2 3 4 5 6 7 8 9 10  # 内部循环
     10   do
     11
     12     if [ "$inner" -eq 7 ]
     13     then
     14       continue 2  # 在第2层循环上的continue, 也就是"外部循环".
     15                   # 使用"continue"来替代这句,
     16                   # 然后看一下一个正常循环的行为.
     17     fi
     18
     19     echo -n "$inner "  # 7 8 9 10 将不会被echo.
     20   done
     21   # --------------------------------------------------------------------
     22   # 译者注: 如果在此处添加echo的话, 当然也不会输出.
     23 done
     24
     25 echo; echo
     26
     27 # 练习:
     28 # 在脚本中放入一个有意义的"continue N".
     29
     30 exit 0

continue N 结构如果用在有意义的场合中, 往往都很难理解, 并且技巧性很高. 所以最好的方法就是尽量避免使用它.

测试与分支(case与select结构)

case 和 select 结构在技术上说并不是循环, 因为它们并不对可执行代码块进行迭代. 但是和循环相似的是, 它们也依靠在代码块顶部或底部的条件判断来决定程序的分支.

在代码块中控制程序分支

case (in) / esac

在shell中的case结构与C/C++中的switch结构是相同的. 它允许通过判断来选择代码块中多条路径中的一条. 它的作用和多个if/then/else语句的作用相同, 是它们的简化结构, 特别适用于创建菜单.

    case "$variable" in

     "$condition1" )
     command...
     ;;

     "$condition2" )
     command...
     ;;

    esac
  • 对变量使用""并不是强制的, 因为不会发生单词分割。
  • 每句测试行, 都以右小括号)来结尾。
  • 每个条件判断语句块都以一对分号结尾 ;;。
  • case块以esac (case的反向拼写)结尾。

例子 19. 使用case

  1 #!/bin/bash
  2 # 测试字符串范围.
  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      #  允许字符串的范围出现在[中括号]中,
 13           #+ 或者出现在POSIX风格的[[双中括号中.
 14
 15 #  在这个例子的第一个版本中,
 16 #+ 测试大写和小写字符串的工作使用的是
 17 #+ [a-z] 和 [A-Z].
 18 #  这种用法在某些特定场合的或某些Linux发行版中不能够正常工作.
 19 #  POSIX 的风格更具可移植性.
 20 #  感谢Frank Wang指出了这点.
 21 exit 0

例子 20. 使用case来创建菜单

  1 #!/bin/bash
  2
  3 # 未经处理的地址资料
  4
  5 clear # 清屏.
  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 # 注意, 变量是被""引用的.
 21
 22   "E" | "e" )
 23   # 接受大写或者小写输入.
 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 # 注意, 每个选项后边都要以双分号;;结尾.
 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 # 后边的 Smith 和 Zane 的信息在这里就省略了.
 48
 49           * )
 50    # 默认选项.
 51    # 空输入(敲回车RETURN), 也适用于这里.
 52    echo
 53    echo "Not yet in database."
 54   ;;
 55
 56 esac
 57
 58 echo
 59
 60 #  练习:
 61 #  -----
 62 #  修改这个脚本, 让它能够接受多个输入,
 63 #+ 并且能够显示多个地址.
 64
 65 exit 0

例子 21. 使用命令替换来产生case变量

  1 #!/bin/bash
  2 # case-cmd.sh: 使用命令替换来产生"case"变量.
  3
  4 case $( arch ) in   # "arch" 返回机器体系的类型.(me:一些系统中找不到这个命令或变量)
  5                     # 等价于 '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

case结构也可以过滤通配(globbing)模式的字符串.

例子 22. 简单的字符串匹配

  1 #!/bin/bash
  2 # match-string.sh: 简单的字符串匹配
  3
  4 match_string ()
  5 {
  6   MATCH=0
  7   NOMATCH=90
  8   PARAMS=2     # 此函数需要2个参数.
  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     # 参数个数错误.
 28 echo $?             # 91
 29
 30 match_string $a $b  # 不匹配
 31 echo $?             # 90
 32
 33 match_string $b $d  # 匹配
 34 echo $?             # 0
 35
 36
 37 exit 0

select

select结构是建立菜单的另一种工具, 这种结构是从ksh中引入的.

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

提示用户输入选择的内容(比如放在变量列表中). 注意: select命令使用PS3提示符, 默认为(#?), 当然, 这可以修改.

例子 23. 使用select来创建菜单

      1 #!/bin/bash
      2
      3 PS3='Choose your favorite vegetable: ' # 设置提示符字串.
      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
     12   break  # 如果这里没有 'break' 会发生什么?
     13 done
     14
     15 exit 0

上面程序执行的情况如下:

$ ./select.sh 

1) beans
2) carrots
3) potatoes
4) onions
5) rutabagas
Choose your favorite vegetable: 1 # 注:输入的是 “beans” 前面的序号。如果这里输入的是 “beans”,情况会怎样?

Your favorite veggie is beans.

如果忽略了in list列表, 那么select命令将会使用传递到脚本的命令行参数($@), 或者是函数参数(当select是在函数中时).

与忽略in list的

    for variable [in list]

结构比较一下.

例子 24. 使用函数中的select结构创建菜单

      1 #!/bin/bash
      2
      3 PS3='Choose your favorite vegetable: '
      4
      5 echo
      6
      7 choice_of()
      8 {
      9 select vegetable
     10 # [in list]被忽略, 所以'select'使用传递给函数的参数.
     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 #         传递给choice_of()的参数
     23
     24 exit 0

经典示例:使用间接变量引用的简单数据库应用(应用到“间接变量引用”和“select”结构)

  1 #!/bin/bash
  2 # resistor-inventory.sh
  3 # 使用间接变量引用的简单数据库应用.
  4
  5 # ============================================================== #
  6 # 数据
  7
  8 B1723_value=470                                   # 欧姆
  9 B1723_powerdissip=.25                             # 瓦特
 10 B1723_colorcode="yellow-violet-brown"             # 颜色
 11 B1723_loc=173                                     # 位置
 12 B1723_inventory=78                                # 数量
 13
 14 B1724_value=1000
 15 B1724_powerdissip=.25
 16 B1724_colorcode="brown-black-red"
 17 B1724_loc=24N
 18 B1724_inventory=243
 19
 20 B1725_value=10000
 21 B1725_powerdissip=.25
 22 B1725_colorcode="brown-black-orange"
 23 B1725_loc=24N
 24 B1725_inventory=89
 25
 26 # ============================================================== #
 27
 28
 29 echo
 30
 31 PS3='Enter catalog number: '
 32
 33 echo
 34
 35 select catalog_number in "B1723" "B1724" "B1725"
 36 do
 37   Inv=${catalog_number}_inventory
 38   Val=${catalog_number}_value
 39   Pdissip=${catalog_number}_powerdissip
 40   Loc=${catalog_number}_loc
 41   Ccode=${catalog_number}_colorcode
 42
 43   echo
 44   echo "Catalog number $catalog_number:"
 45   echo "There are ${!Inv} of [${!Val} ohm / ${!Pdissip} watt] resistors in stock."
 46   echo "These are located in bin # ${!Loc}."
 47   echo "Their color code is \"${!Ccode}\"."
 48
 49   break
 50 done
 51
 52 echo; echo
 53
 54 # 练习:
 55 # -----
 56 # 1) 重写脚本, 使其从外部文件读取数据.
 57 # 2) 重写脚本,
 58 #+   用数组来代替间接变量引用,
 59 #    因为使用数组更简单, 更易懂.
 60
 61
 62 # 注:
 63 # ---
 64 #  除了最简单的数据库应用, 事实上, Shell脚本本身并不适合于数据库应用.
 65 #+ 因为它太依赖于工作环境和机器的运算能力.
 66 #  更好的办法还是使用支持数据结构的本地语言,
 67 #+ 比如C++或者Java(或者甚至可以是Perl).
 68
 69 exit 0
阅读(1807) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~