第二十九章:流量控制:使用while/until循环

在上一章中,我们开发了一个菜单驱动程序来生成各种系统信息。该程序工作正常,但仍然存在严重的可用性问题。它只执行一个选项,然后终止。更糟糕的是,如果做出了无效的选择,程序将以错误终止,而不会给用户重试的机会。如果我们能以某种方式构建程序,使其能够一遍又一遍地重复菜单显示和选择,直到用户选择退出程序,那就更好了。

在本章中,我们将介绍一个称为循环(looping)的编程概念,它可用于使程序的部分重复。shell提供了三个用于循环的复合命令。我们将在本章中介绍其中的两个,在后面的章节中介绍第三个。

第二十九章:流量控制:使用while/until循环循环whilebreakcontinueselectuntil使用循环读取文件总结

循环

日常生活充满了重复的活动。每天上班、遛狗和切胡萝卜都是需要重复一系列步骤的任务。让我们考虑切一根胡萝卜。如果我们用伪代码表示此活动,它可能看起来像这样:

1.拿砧板 2.拿刀 3.把胡萝卜放在砧板上 4.举起刀 5.推动胡萝卜 6.切胡萝卜 7.如果整根胡萝卜都切成薄片,那就退出;否则转到步骤4

步骤4到7形成一个循环。重复循环中的操作,直到达到“整根胡萝卜切片”的条件。

while

bash可以表达类似的想法。假设我们想按从1到5的顺序显示五个数字。bash脚本可以按如下方式构造:

执行时,此脚本显示以下内容:

while 命令的语法如下:

while commands; do commands; done

以上脚本可以简化为一行命令:

类似 ifwhile 评估命令列表的退出状态。只要退出状态为零,它就会在循环内执行命令。在前面的脚本中,创建了变量 count 并为其分配了初始值1。 while 命令评估 [[ ]] 复合命令的退出状态。只要 [[ ]] 命令返回退出状态为零,循环中的命令就会被执行。在每个循环结束时,重复 [[ ]] 命令。在循环的五次迭代后, count 的值增加到6, [[ ]] 命令不再返回退出状态为零,循环终止。程序继续执行循环后的下一个语句。

我们可以使用 while 循环来改进上一章的读取菜单程序。

通过将菜单封闭在while循环中,我们可以让程序在每次选择后重复显示菜单。只要 REPLY 不等于0,循环就会继续,菜单就会再次显示,让用户有机会进行另一次选择。在每个操作结束时,都会执行 sleep 命令,因此程序将暂停几秒钟,以便在屏幕被清除和菜单重新显示之前看到选择的结果。一旦 REPLY 等于0,表示“退出”选择,循环终止,执行继续,完成以下行。

breakcontinue

bash提供了两个内置命令,可用于控制循环内的程序流。

在这里,我们看到一个包含 breakcontinuewhile-menu 程序版本:

在这个版本的脚本中,我们通过使用 true 命令向 while 提供退出状态来设置一个无休止的循环(一个永远不会自行终止的循环)。由于 true 总是以退出状态为零退出,因此循环永远不会结束。这是一种令人惊讶的常见脚本技术。由于循环永远不会自行结束,因此程序员有责任在适当的时候提供一些打破循环的方法。在此脚本中,当选择0选项时,使用 break 命令退出循环。 continue 命令已包含在其他脚本选项的末尾,以实现更高效的执行。通过使用 continue ,脚本将跳过识别选择时不需要的代码。例如,如果选择并识别了1个选项,则没有理由测试其他选项。

select

这将是一个很好的时机来提及用于创建循环菜单的 select shell内置。它的语法如下:

select var in [string...;] do commands ; done

其中 var 是一个变量, string 是菜单选项的文本。

select 执行时,它会显示 sting ,后跟PS3(提示字符串prompt string 3)变量的内容,作为用户输入的提示。一旦做出选择,它就会使用用户的输入设置 REPLY 变量(就像读取一样),并在变量 var 中返回与选择相关的字符串。一旦设置了值,就会执行命令,并再次显示另一个选择的提示。这听起来有点令人困惑,但我们可以用这个小脚本来演示:

首先,我们用所需的提示字符串设置 PS3 变量的内容。接下来,我们执行 select 。在这个例子中,我们有五个字符串,虽然我们使用了单个单词作为字符串,但我们可以使用任何类型的引用文本。对于我们的命令,我们只需回显 select 所做的分配。我们还测试了变量 my_choice 的内容,看看用户是否选择了“Quit”选项,如果是,我们执行一个中断以退出循环。

select 首次执行时,它会显示我们的每个字符串,前面是一个数字,后面是提示字符串。用户接下来输入代表所需选择的数字。然后, select 命令将 REPLY 变量设置为包含用户输入的任何内容以及相应的字符串(如果有的话)。

在这里,我们看到用户输入了“1”, echo 命令显示了 REPLYmy_choice 变量的值。 select 将重复显示提示字符串,直到用户输入“5”。如果用户输入了无效值, select 会将 my_choice 设置为空字符串。如果用户只是键入 Enter ,这将导致select重新开始并重新显示菜单选项列表。

select 循环将无限期地继续,直到遇到中断命令或用户键入 ctrl-d 表示文件结束。

select 的一个有趣特性是,它不会在标准输出上显示菜单选项或提示字符串,而是使用标准错误。这实际上很方便,因为它允许重定向循环中命令所做的实际工作,例如:

当我们执行此重定向时,菜单和提示仍会显示,但 echo 命令的输出会被重定向。

让我们制作一个系统信息脚本的替代版本,用 select 替换之前的 while 循环。

在我们的备用脚本中,我们设置 PS3 变量,然后用四个字符串调用 select 。虽然我们随后可以通过 select 测试 str 变量集,但测试 REPLY 变量并采取相应行动更容易。在循环结束时,我们检查 str 变量的长度是否为零,表示值无效。

那么,我们应该使用哪种方法来构建菜单呢? select 命令很有趣,但除了在菜单显示中使用标准错误外,它并没有真正为我们节省多少编码工作,而且它极大地限制了菜单显示的视觉设计。

until

until 命令与 while 非常相似,除了当遇到非零退出状态时,它不会退出循环,而是相反。until loop 会一直继续,直到它收到零退出状态。在 while-count 脚本中,只要 count 变量的值小于或等于5,我们就继续循环。通过使用 until 对脚本进行编码,我们可以得到相同的结果。

通过将测试表达式更改为 $count -gt 5until 将在正确的时间终止循环。选用 whileuntil 循环的决定,通常取决于哪一种能编写出最清晰测试的循环。

使用循环读取文件

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

为了将文件重定向到循环,我们将重定向运算符放在 done 语句之后。循环将使用 read 从重定向文件中输入字段。读取命令将在读取每一行后退出,退出状态为零,直到到达文件末尾。此时,它将以非零退出状态退出,从而终止循环。也可以将标准输入管道到回路中。

在这里,我们获取 sort 命令的输出并显示文本流。但是,重要的是要记住,由于管道将在子shell中执行循环,因此当循环终止时,在循环中创建或分配的任何变量都将丢失。

总结

随着循环的引入以及我们之前遇到的分支、子程序和序列,我们已经介绍了程序中使用的主要类型的流控制。bash还有更多的技巧,但它们是对这些基本概念的改进。