本节书摘来自异步社区《UNIX编程环境》一书中的第5章,第5.3节,作者:【美】Brian W. Kernighan , Rob Pike著,更多章节内容可以访问云栖社区“异步社区”公众号查看
5.3 while和until循环:观察情况
在第3章中,for循环用于一些简单的迭代程序。一般地,for对一组文件名进行循环,如在for i in .c中;或对shell程序的所有参数进行循环,如for i in $。但实际上,shell中的for循环可以有更大的用途,如在which里的for循环。
shell有3种循环:for、while和until。其中for是最常用的一种循环,对于循环变量集中的每一个元素,它执行一次循环体内的一组命令。最常用的循环变量是文件名。while循环和until循环利用命令的退出状态来控制循环体内命令的执行。循环体一直执行,直至作为条件的命令返回状态为非0(对于while循环)或0(对于until循环)。while循环和until循环除了对命令的退出状态进行不同的解释之外,其他完全相同。下面是各种循环的基本格式:
在第二种形式的for循环中,空的循环参数表示$*,这是常用的缩写形式。
控制while或until的条件命令可以是任何命令。例如,下面是一个观察某人(比如Mary)登录情况的循环:
其中sleep命令用于挂起60秒,它总能正确执行(除非发生中断),因此总返回“成功”,这样,这个循环每分钟检查一次,查看Mary是否已登录。
这一版本有个缺点,如果Mary已经登录,也必须等60秒后才能知道Mary登录的信息。还有,一旦Mary已经登录到系统中,你就会每60秒接收到一个报告。可以将这个程序修改一下,使用until循环来编写,此时,如果Mary已登录到系统,你不需延迟就可以立即得到Mary的登录信息:
这是一个更有意思的条件。如果Mary已经登录,who¦grep mary就打印出who列表中mary这一行,并返回“真”值,因为grep返回的状态表示它是否找到了匹配的内容,管道命令的退出状态是最后一个元素的退出状态。
最后,我们可以将这条命令包装起来,给它取一个名字,并安装到系统中:
来同时观察多个人的登录情况。
作为一个更复杂的例子,我们考虑一个功能增强的who命令:它不仅能观察所有人的登录情况,并能及时报告用户登录或退出系统的情况。它的基本结构很简单:每分钟运行一次who命令,并与一分钟前的输出情况相比较,如果有不同,则报告发生的改变。who命令的输出将保存在一个文件里,我们将这个文件保存在目录/tmp中。为了区分我们的文件和其他进程的文件,可以将shell变量$$(shell命令的进程标识码)放在文件名里,这是一种常用的方法。将命令的名字编码到临时文件名中是系统管理员常用的手段之一。各种命令(包括watchwho命令)常常把文件放在/tmp目录中,最好了解是哪个命令在这么做。
“:”是shell的内部命令,它仅仅评估它的参数,然后返回“真”。在这里,我们也可以用true命令代替它。true命令仅仅返回一个“真”的退出状态。(还有一个false命令。)但是,“:”不需要执行文件系统中的命令,所以它比true命令效率更高一些。
diff命令的输出用<和>区分来自两个文件中的不同数据;awk程序处理diff输出,并以更容易理解的格式显示出来。注意,整个while循环的结果通过管道送入awk,而不是每分钟刷新一次awk。sed不适用于这种处理情况,因为它的输出总是比输入落后一行,即总有一行的输入正在处理,没有打印,这样会导致不必要的延迟。
old生成时是一个空文件,所以watchwho第一次输出的内容是当前所有登录的用户清单。如果用命令who>$old建立old的初值,那么watchwho将只打印用户登录的变化情况,这取决于习惯的不同。
另一个循环程序可以定时地查看信箱,当信箱的内容变化时,程序打印“you have mail”。对shell的内部机制来说,使用MAIL变量是很有用的方法,我们的实现使用了shell变量替代文件,来解释这种差别。
这里再次用到了awk程序,确保只有mailbox增长时才打印消息,而不是变化时,否则,当你删除邮件信息时也会收到消息。(shell的内部版本就受到这一缺点的限制。)
时间间隔仍然设为60秒。但在命令行提供了一个参数,如果键入下面的命令:
就使用新的时间参数。如果提供了时间参数,shell变量t被设为对应的时间间隔,如果提供时间参数,默认的时间间隔为60秒,由下面这一行确定:
这是shell的另一个特点。
${var}等价于$var,它可以用来避免变量名与字母或数字组成的字符串相混杂所引起的问题:
大括号内的某些特殊字符可以指定对变量进行特殊处理。如果该变量没有定义,并且在变量名之后带有一个问号,则打印问号后面的字符串,然后退出shell(除非是交互情况)。如果没有提供问号后的信息,则打印标准的输出信息:
注意,shell产生的信息中总是包括未定义变量的名字。
另一种形式是变量${var-thing}。当var有定义时,其值为$var;当var没有定义时,其值为thing。${var=thing}的情形与其相仿,但会把$var的值置为thing:
变量取值规则在表5-3中列出。
回到我们原来的例子:
把t设为$1,如果没有提供参数,t设为60。