Shell—— 24.while

使用while循环尽量要让条件运行到可以退出循环,否则无限循环。一般都在命令体部分加上变量的改变行为。
语法:
while test_cmd_list; do cmd_list; done
首先执行test_cmd_list中的命令,当test_cmd_list的最后一个命令的状态码为0时,将执行一次cmd_list,然后回到循环的开头继续执行test_cmd_list。只有test_cmd_list中最后一个测试命令的状态码非0时,循环才会退出。

[root@master ~]# let i=1,sum=0;while [ $i -le 10 ];do let sum=sum+i;let ++i;done;echo $sum         
55

在此例中,test_cmd_list中只有一个命令[ $i -le 10 ],所以它的状态直接决定整个循环何时退出。
test_cmd_list中可以是多个命令,但千万要考虑清楚,是否要让决定退出循环的测试命令处在列表的尾部,否则将进入无限循环。

[root@master ~]# let i=1,sum=0;while echo $i;[ $i -le 10 ];do let sum=sum+i;let ++i;done;echo $sum 
1
2
3
4
5
6
7
8
9
10
11
55

对于while循环,有另外两种常见的写法:
1.1.test_cmd_list部分使用一个冒号":“或者true命令,使得while进入无限循环。
while :;do # 或者"while true;do”

done
1.2 使用read命令从标准输入中按行读取值,然后保存到变量line中(既然是read命令,所以可以保存到多个变量中),读取一行是一个循环。
由于标准输入既可以来源于重定向,也可以来源于管道(本质还是重定向),所以有几种常见的写法:
写法一:使用管道传递内容,这是最烂的写法
echo “abc xyz” | while read field1 field2 # 按IFS分割,并赋给两个变量
do

done
写法二:
while read line
do

done <<< “abc xyz”
写法三:从文件中读取内容
while read line
do

done </path/filename
既然是读取标准输入,于是还可以衍生出几种写法:
方法四:while read var;do …;done < <(cmd_list) # 采用进程替换
方法五:exec <filename;while read var;do …;done # 改变标准输入
尽管写法有多种,但注意,它们并不等价。
陷阱一:
方法一中使用的是管道符号,这使得while语句在子shell中执行,这意味着while语句内部设置的变量、数组、函数等在循环外部都不再生效。例如:
#!/bin/bash
echo “abc xyz” | while read line
do
new_var=$line
done
echo the variable new_var is null: n e w v a r ? 该 脚 本 的 执 行 结 果 中 , new_var? 该脚本的执行结果中, newvar?new_var的值将为空。
使用除写法一外的任意一种写法,在while循环外部都能继续获得while内的环境。例如,使用写法二的here string代替写法一:
#!/bin/bash
while read line
do
new_var=$line
done <<< “abc xyz”
echo the variable new_var is null: $new_var?
如果没注意写法一中while是在子shell运行,很可能会一直疑惑,为什么在while循环里设置好的变量或数组在循环一结束就成了空值呢。
陷阱二:
关于这几种while循环的写法,还有一点要注意:写法一和写法四传递数据的源都是一个单独的进程,它们传递的数据一被while循环读取,所有数据就丢弃了,而以实体文件作为重定向传递的数据,while读取了之后并不会丢弃。更标准一些的说法是,当标准输入是非实体文件时(如管道传递的、独立进程产生的)只供一次读取;当标准输入是直接重定向实体文件时,可供多次读取,但只要某一次读取了该文件的全部内容就无法再提供读取。
举个例子,老师让我们听写10个单词,而我记忆力比较烂,他念完10个单词时我可能只写出了3个,剩余的7个因为记不住就没法再写出来。但如果我有小抄,我就可以慢悠悠的一个一个写,写了一个还可以等一段时间再写第二个,但当我写完10个之后,小抄这种东西就应该销毁掉。
回到IO重定向上,无论什么数据资源,只要被读取完毕或者主动丢弃,那么该资源就不可再得。①对于独立进程传递的数据(管道左侧进程产生的数据、进程替换产生的数据),它们都是"虚拟"数据,要不被一次读取完毕,要不读一部分剩余的丢弃,这是真正的一次性资源。②而实体文件重定向传递的数据,只要不是一次性被全部读取,它就是可再得资源,直到该文件数据全部读取结束,这是"伪"一次性资源。其实①是进程间通信时数据传递的现象,只不过这个问题容易被人忽略。
大多数时候,独立进程传递的数据和文件直接传递的数据并没有什么区别,但有些命令可以标记当前读取到哪个位置,使得下次该命令的读取动作可以从标记位置处恢复并继续读取,特别是这些命令用在循环中时。据我到目前的总结,这样的命令有"head -n N"和"grep -m",经测试,tail并没有位置标记的功能,因为tail读取的是后几行,所以它必然要读取到最后一行并计算要输出的行,所以tail的性能比head要差。
说了这么多,现在终于开始验证。下面的循环中,本该head每次读取2行,但实际执行结果中总共就只读取了一次2行。

[root@master ~]# i=0
[root@master ~]# cat /etc/fstab | while head -n 2 ; [[ "$i" -le 3 ]];do echo $i;let ++i;done     

#
0
1
2
3

使用进程替换的结果是一样的。

[root@master ~]# i=0
[root@master ~]# while head -n 2; [[ "$i" -le 3 ]];do echo $i;let ++i;done < <(cat /etc/fstab)

#
0
1
2
3

但如果是直接将实体文件进行重定向传递给head,则结果和上面的不一样。

[root@master ~]# i=0;while head -n 2 ; [[ "$i" -le 3 ]];do echo $i;let ++i;done < /etc/fstab

#
0
# /etc/fstab
# Created by anaconda on Thu May 11 04:17:44 2017
1
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
2
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
3
UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 /                       xfs     defaults        0 0
UUID=367d6a77-033b-4037-bbcb-416705ead095 /boot                   xfs     defaults        0 0

可以看到结果中每次读取两行并echo一次"$i",而且每次读取的两行是不同的,后一次读取的两行是从前一次读取结束的地方开始的,这是因为head有"读取到指定行数后做上位置标记"的功能。
要确定命令、工具是否具有做位置标记的能力,只需像下面例子一样做个简单的测试。以head和sed为例,即使sed的"q"命令能让sed匹配到内容就退出,但却不做位置标记,而且数据资源使用一次就丢弃,所以sed测试中,第二个sed完全是废的,因为/etc/fstab这个资源在被第一个sed读取后就丢掉了。

[root@master ~]# (head -n 2;head -n 2) </etc/fstab 

#
# /etc/fstab
# Created by anaconda on Thu May 11 04:17:44 2017

[root@master ~]# (sed -n /default/'{p;q}' ;sed -n /default/'{p;q}') </etc/fstab     
UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 /                       xfs     defaults        0 0

其实在实际应用过程中,这根本就不是个问题,因为搜索和处理文本数据的工具虽然不少,但绝大多数都是用一次文本就"丢"一次,几乎不可能因此而产生问题。之所以说这么多废话,主要是想说上面的5种while写法中,使用最广泛的写法一虽然最简单、方便,但其实是最烂的一种。
————Blueicex 2020/03/18 19:50 blueice1980@126.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值