第33章:流程控制:for 循环
在关于流程控制的最后一章中,我们将学习另一个 shell 循环结构。for 循环(for loop)不同于 while
和 until
循环,它提供了在循环内处理序列的方法。这在编程时会变得很有用。因此,for
循环在 bash
脚本中广为流行。
很自然地,实现 for
循环,需要使用 for
复合命令。在 bash
中,for
有两种形式。
for:传统的 Shell 形式
原始的 for
命令句法如下:
其中 variable
是一个会在循环执行过程中自增的变量的名称,words
是一个可选的会被按序分配到 variable
中的列表项,commands
是会在每一次循环迭代中被执行的命令。
for
命令在命令行中也很有用。我们可以很方便地演示其如何工作。
上例中,有四个词给到 for
:A
、B
、C
和 D
。循环用这四个词执行了四次。每次执行循环,就分配一个词给变量 i
。在循环内部,有一个 echo
命令,会显示 i
的值,以显示当前的分配。和 while
及 until
循环一样,done
关键词关闭循环。
for
真正强大的特性是有众多有趣的途径可以创建 words
列表。例如,可以用花括号扩展,如:
或者可以使用路径名扩展,如下:
路径名扩展提供了一个漂亮干净的路径名列表,使其可以在循环中被处理。需要的一个预防措施是要检查这扩展事实上匹配了什么。默认情况下,如果扩展没有匹配任何文件,则返回通配符自身(在上例中是 "distros*.txt")。要防止此类情况,我们可以如下修改上面的代码:
通过增加一个对文件存在与否的测试,可以忽略失败的扩展。
另一个常见的制造 words
的方法是命令替换。
在这个例子中,我们寻找一个文件中最长的字符串。当在命令行上给出一个或更多文件名时,程序会使用 strings
程序(包含在 GNU binutils
包中)在每个文件中生成一个可读文本列表 "words"。for
循环反过来处理每个单词并判断当前单词是否是已找过单词中最长的一个。当循环得出结论,就会显示最长的那个单词。
这里有一件事需要记住,与通常的练习相反,我们没有把命令替换 $(strings "$1")
放在双引号内。这是因为我们实际上是想分词,以便给出一份列表。如果我们把命令替换放在引号内,则仅会产生单个单词,它会包含文件中每个字符串。那不是我们想要查找的。
如果 for
命令中可选的 words
部分被省略了,for
默认去处理位置参数。我们来使用这种方法修改 longest-word
脚本:
我们可以看到,已经用 for
替换了 while
,变更了最外层的循环。在省略了 for
命令中的 words
列表后,用位置参数替代了。在循环内,前面例子中的变量 i
已经被改成 j
了。同时还移除了 shift
。
为什么是 i?
可能你已经注意到了上面的
for
循环示例中,总是选择i
作为变量。为何?除了传统,实际上没有什么特殊的理由。用在for
里的变量可以是任意合法的变量名,但是i
最常用,之后就是j
和k
。这个传统的基础来自 Fortran 编程语言。在 Fortran 中,未声明的变量从字母 I、J、K、L、M 开始,类型自动设置为整数,而其它字母开头的变量类型则为实数(有小数部分的数字)。这一行为引领程序员使用 I、J、K 作为循环变量,因为在需要临时变量(通常是循环变量)时,使用它们的工作量较小。
这也引出了下面这句基于 Fortran 的妙语:
「 上帝是实数,除非声明为整数。」
for:C 语言形式
近几个版本的 bash
已经加入了 for
命令的第二种句法形式,和 C 编程语言的形式类似。许多其它语言也支持这种形式。
这里,expression1
、expression2
和 expression3
都是算术表达式,commands
是在循环中每次迭代都会被执行的命令。
就行为而言,这种形式等效于下列的结构:
expression1
用来初始化循环条件,expression2
用来确定何时结束循环,而 expression3
在每次循环迭代的最后被执行。
这里有个典型的应用:
当执行时,产生下列输出:
本例中,expression1
以数值 0
初始化变量 i
,expression2
的作用是,只要变量 i
的值小于 5
,就继续循环,而 expression3
在每次循环重复时会使变量 i
增长 1
。
for
的 C 语言形式在任何需要数字序列的时候都很有用。在下面两章中我们可以看到。
总结
掌握了 for
命令,我们现在可以对 sys_info_page
脚本做最后的改进。当前,report_home_space
函数是这样的:
接下来,我们将重写这部分,以提供针对每个用户家目录更多的细节,并包含所有文件和子目录的数目。
本次改写应用了迄今为止我们学到的许多知识。我们还是测试其是否为超级用户,不过,不是执行if
部分中的整套行为,而是设置了一些随后用在 for
循环里的变量。我们给函数加入了几个局部变量,并用 printf
格式化了一些输出。
扩展阅读
Last updated
Was this helpful?