流程控制:while/until 循环

    在这一章中,我们将看一个叫做循环的程序概念,其可用来使程序的某些部分重复。shell 为循环提供了三个复合命令。本章我们将查看其中的两个命令,随后章节介绍第三个命令。

    日常生活中充满了重复性的活动。每天去散步,遛狗,切胡萝卜,所有任务都要重复一系列的步骤。让我们以切胡萝卜为例。如果我们用伪码表达这种活动,它可能看起来像这样:

    1. 准备切菜板

    2. 准备菜刀

    3. 把胡萝卜放到切菜板上

    4. 提起菜刀

    5. 向前推进胡萝卜

    6. 如果切完整个胡萝卜,就退出,要不然回到第四步继续执行

    从第四步到第七步形成一个循环。重复执行循环内的动作直到满足条件“切完整个胡萝卜”。

    30.2 while

    bash 能够表达相似的想法。比方说我们想要按照顺序从1到5显示五个数字。可如下构造一个 bash 脚本:

    当执行的时候,这个脚本显示如下信息:

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. Finished.

    while 命令的语法是:

    和 if 一样, while 计算一系列命令的退出状态。只要退出状态为零,它就执行循环内的命令。在上面的脚本中,创建了变量 count ,并初始化为1。 while 命令将会计算 test 命令的退出状态。只要 test 命令返回退出状态零,循环内的所有命令就会执行。每次循环结束之后,会重复执行 test 命令。第六次循环之后, count 的数值增加到6, test 命令不再返回退出状态零,且循环终止。程序继续执行循环之后的语句。

    我们可以使用一个 while 循环,来提高前面章节的 read-menu 程序:

    1. #!/bin/bash
    2. # while-menu: a menu driven system information program
    3. DELAY=3 # Number of seconds to display results
    4. while [[ $REPLY != 0 ]]; do
    5. cat <<- _EOF_
    6. Please Select:
    7. 1. Display System Information
    8. 2. Display Disk Space
    9. 3. Display Home Space Utilization
    10. 0. Quit
    11. _EOF_
    12. read -p "Enter selection [0-3] > "
    13. if [[ $REPLY == 1 ]]; then
    14. echo "Hostname: $HOSTNAME"
    15. uptime
    16. sleep $DELAY
    17. fi
    18. if [[ $REPLY == 2 ]]; then
    19. df -h
    20. sleep $DELAY
    21. fi
    22. if [[ $REPLY == 3 ]]; then
    23. if [[ $(id -u) -eq 0 ]]; then
    24. echo "Home Space Utilization (All Users)"
    25. du -sh /home/*
    26. else
    27. echo "Home Space Utilization ($USER)"
    28. du -sh $HOME
    29. sleep $DELAY
    30. fi
    31. echo "Invalid entry."
    32. sleep $DELAY
    33. fi
    34. done
    35. echo "Program terminated."

    30.3 跳出循环

    bash 提供了两个内部命令,它们可以用来在循环内部控制程序流程。 break 命令立即终止一个循环,且程序继续执行循环之后的语句。 continue 命令导致程序跳过循环中剩余的语句,且程序继续执行下一次循环。这里我们看看采用了 break 和 continue 两个命令的 while-menu 程序版本:

    在这个脚本版本中,我们设置了一个无限循环(就是自己永远不会终止的循环),通过使用 true 命令为 while 提供一个退出状态。因为 true 的退出状态总是为零,所以循环永远不会终止。这是一个令人惊讶的通用脚本编程技巧。因为循环自己永远不会结束,所以由程序员在恰当的时候提供某种方法来跳出循环。此脚本,当选择”0”选项的时候,break 命令被用来退出循环。continue 命令被包含在其它选择动作的末尾,来提高程序执行的效率。通过使用 continue 命令,当一个选项确定后,程序会跳过不需执行的其他代码。例如,如果选择了选项”1”,则没有理由去测试其它选项。

    until 命令与 while 非常相似,除了当遇到一个非零退出状态的时候, while 退出循环,而 until 不退出。一个 until 循环会继续执行直到它接受了一个退出状态零。在我们的 while-count 脚本中,我们继续执行循环直到 count 变量的数值小于或等于5。我们可以得到相同的结果,通过在脚本中使用 until 命令:

    1. #!/bin/bash
    2. # until-count: display a series of numbers
    3. count=1
    4. until [ $count -gt 5 ]; do
    5. echo $count
    6. count=$((count + 1))
    7. done
    8. echo "Finished."

    通过把 test 表达式更改为 $count -gt 5 , until 会在正确的时间终止循环。至于使用 while 循环还是 until 循环,通常是选择其 test 判断条件最容易写的那种。

    30.5 使用循环读取文件

    while 和 until 能够处理标准输入。这就可以使用 while 和 until 处理文件。在下面的例子中,我们将显示在前面章节中使用的 distros.txt 文件的内容:

    为了重定向文件到循环中,我们把重定向操作符放置到 done 语句之后。循环将使用 read 从重定向文件中读取字段。这个 read 命令读取每个文本行之后,将会退出,其退出状态为零,直到到达文件末尾。到时候,它的退出状态为非零数值,因此终止循环。也有可能把标准输入管道到循环中。

    1. #!/bin/bash
    2. # while-read2: read lines from a file
    3. sort -k 1,1 -k 2n distros.txt | while read distro version release; do
    4. printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
    5. $distro \
    6. $version \
    7. done

    这里我们接受 sort 命令的标准输出,然后显示文本流。然而,因为管道将会在子 shell 中执行循环,当循环终止的时候,循环中创建的任意变量或赋值的变量都会消失,记住这一点很重要。

    拓展阅读

    • Linux 文档工程中的 Bash 初学者指南一书中介绍了更多的 while 循环实例: