shell中while循环的陷阱
bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
在写while循环的时候,发现了一个问题,在while循环内部对变量赋值、定义变量、数组定义等等环境,在循环外面失效。
一个简单的测试脚本如下:
#!/bin/bash
echo "abc xyz" | while read line
do
new_var=$line
done
echo new_var is null: $new_var?
执行结果证明,$new_var的结果是空值。
问题出在管道上。先看看下面的内容。
while循环的写法有好几种,它的语法结构为:
while test_cmd_list; do cmd_list; done
但更经常地,while循环更多地用于读取标准输入的内容来实现循环。有以下几种写法:
写法一:使用管道传递内容,这是用的最多、但却最烂的写法
echo "abc xyz" | while read line
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语句内部设置的变量、数组、函数等在循环外部都不再生效。这正是文章开头所说的陷阱。更简单的:echo haha | a=5,在命令执行结束后,变量a的值也不再是5。其余4种写法,while语句都不在子shell中执行,因此都不会出现文章开头所说的问题。
例如,使用写法二的here string代替写法一:
#!/bin/bash
while read line
do
new_var=$line
done <<< "abc xyz"
echo new_var is null: $new_var?
或者使用写法四的进程替换:
#!/bin/bash
while read line
do
new_var=$line
done < <(echo "abc xyz")
echo new_var is null: $new_var?
陷阱二:
关于这几种while循环的写法,还有一点要注意:写法一和写法四传递数据的源都是一个单独的进程,它们传递的数据一被while循环读取,所有数据就丢弃了,而以实体文件作为重定向传递的数据,while读取了之后并不会丢弃。更标准一些的说法是,当标准输入是非实体文件时(如管道传递的、独立进程产生的)只供一次读取;当标准输入是直接重定向实体文件时,可供多次读取,但只要某一次读取了该文件的全部内容就无法再提供读取。
举个例子,老师让我们听写10个单词,而我记忆力比较烂,他念完10个单词时我可能只写出了3个,剩余的7个因为记不住就没法再写出来。但如果我有小抄,我就可以慢悠悠的一个一个写,写了一个还可以等一段时间再写第二个,但当我写完10个之后,小抄这种东西就应该销毁掉。
回到IO重定向上,无论什么数据资源,只要被读取完毕或者主动丢弃,那么该资源就不可再得。①对于独立进程传递的数据(管道左侧进程产生的数据、进程替换产生的数据),它们都是"虚拟"数据,要不被一次读取完毕,要不读一部分剩余的丢弃,这是真正的一次性资源。②而实体文件重定向传递的数据,只要不是一次性被全部读取,它就是可再得资源,直到该文件数据全部读取结束,这是"伪"一次性资源。其实①是进程间通信时数据传递的现象,只不过这个问题容易被人忽略。
大多数时候,独立进程传递的数据和文件直接传递的数据并没有什么区别,但有些命令可以标记当前读取到哪个位置,使得下次该命令的读取动作可以从标记位置处恢复并继续读取,特别是这些命令用在循环中时。据我到目前的总结,这样的命令有"head -n N"和"grep -m",经测试,tail并没有位置标记的功能。
说了这么多,现在终于开始验证。下面的循环中,本该head每次读取2行,但实际执行结果中总共就只读取了一次2行。
[root@xuexi ~]# i=
[root@xuexi ~]# cat /etc/fstab | while head -n ; [[ "$i" -le ]];do echo $i;let ++i;done #
使用进程替换的结果是一样的。
[root@xuexi ~]# i=
[root@xuexi ~]# while head -n ; [[ "$i" -le ]];do echo $i;let ++i;done < <(cat /etc/fstab) #
但如果是直接将实体文件进行重定向传递给head,则结果和上面的不一样。
[root@xuexi ~]# i=;while head -n ; [[ "$i" -le ]];do echo $i;let ++i;done < /etc/fstab # # /etc/fstab
# Created by anaconda on Thu May :: #
# Accessible filesystems, by reference, are maintained under '/dev/disk' # See man pages fstab(), findfs(), mount() and/or blkid() for more info
# UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 / xfs defaults
UUID=367d6a77-033b--bbcb-416705ead095 /boot xfs defaults
可以看到结果中每次读取两行并echo一次"$i",而且每次读取的两行是不同的,后一次读取的两行是从前一次读取结束的地方开始的,这是因为head有"读取到指定行数后做上位置标记"的功能。
要确定命令、工具是否具有做位置标记的能力,只需像下面例子一样做个简单的测试。以head和sed为例,即使sed的"q"命令能让sed匹配到内容就退出,但却不做位置标记,而且数据资源使用一次就丢弃。
[root@xuexi ~]# (head -n ;head -n ) </etc/fstab #
# /etc/fstab
# Created by anaconda on Thu May ::
[root@xuexi ~]# (sed -n /default/'{p;q}' ;sed -n /default/'{p;q}') </etc/fstab
UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 /                       xfs     defaults         
其实在实际应用过程中,这根本就不是个问题,因为搜索和处理文本数据的工具虽然不少,但绝大多数都是用一次文本就"丢"一次,几乎不可能因此而产生问题。之所以说这么多废话,主要是想说上面的5种while写法中,使用最广泛的写法一虽然最简单、方便,但其实是最烂的一种。
shell中while循环的陷阱的更多相关文章
- shell中的循环
		
shell中的循环 for循环 类似于C语言的步长控制 例如: ;i<=;i++)); ); done 将1到10,依次乘以4,然后打印出来. 这里顺便提一下,shell里面表达式的计算,可以有 ...
 - shell中for循环
		
shell中for循环总结 最常用的就是遍历操作某一类文件,比如批量建索引. for i in `ls` do samtools faidx $i done 注意:for末尾不需要冒号(:),循环的代 ...
 - shell中for循环总结
		
关于shell中的for循环用法很多,一直想总结一下,今天网上看到上一篇关于for循环用法的总结,感觉很全面,所以就转过来研究研究,嘿嘿... 1. for((i=1;i<=10;i++));d ...
 - Shell中的循环语句实例
		
1.for循环语句实例1.1 最基本的for循环 #!/bin/bash for x in one two three four do echo number $x done 注:" ...
 - (八)shell中的循环结构
		
1.for循环(1)要求:能看懂.能改即可.不要求能够完全不参考写出来.因为毕竟嵌入式并不需要完全重新手写shell,系统管理员(服务器运维人员,应用层系统级管理开发的才需要完全掌握shell) 这里 ...
 - Linux shell编程 4 ---- shell中的循环
		
1 for循环 1 for语句的结构 for variable in values; do statement done 2 for循环通常是用来处理一组值,这组值可以是任意的字符串的集合 3 for ...
 - shell中的循环语句
		
for语法格式 for var in list;do commands done 其中list可以包含: 1) 直接写 for alpha in a b c d;do echo $alpha done ...
 - shell中的循环语法
		
shell中的循环语法 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.for循环 1.语法格式1 for 变量 in 值1 值2 值3 ... do ...
 - shell中的循环语句while
		
循环语句的结构: ------------| while 条件 | do | 需要执行的命令 | done | -----------| 例如: 1.while一直循环 2.whi ...
 
随机推荐
- Java Web使用Html5 FormData实现多文件上传
			
前一阵子,迭代一个线上的项目,其中有一个图片上传的功能,之前用的ajaxfileupload.js来实现上传的,不过由于ajaxfileupload.js,默认是单文件上传(虽然可以通过修改源码的方法 ...
 - 拥抱.NET Core系列:Logging (1)
			
在之前我们简单介绍了 .NET Core 中的 DI组件,没来及了解的童鞋可以翻翻我之前的文章. 接下来会对 .NET Core 中的 Logging 进行介绍. 本文中使用了"Micros ...
 - 单片机C语言基础编程源码六则2
			
1.某单片机系统的P2口接一数模转换器DAC0832输出模拟量,现在要求从DAC0832输出连续的三角波,实现的方法是从P2口连续输出按照三角波变化的数值,从0开始逐渐增大,到某一最大值后逐渐减小,直 ...
 - Eclipse简单插件开发-启动时间提示
			
1.新建Plug-in Project 不用改其他选项,直接点击"Next",然后点击"Finish" 2.新建ShowTime.java package ...
 - 怎么在linux ubuntu 上的nginx 绑定域名
			
前一篇介绍了,如果在ubuntu上运行nodejs,毕竟访问的时候都是用ip地址+端口号,但是上production 环境都需要域名的,IP地址当然不行 读过上一篇的朋友知道了,如果在upstart ...
 - 前端模块化工具--webpack使用感受
			
话说前头 webpack前段时间有听说一下,现在已经到了3.x的版本,自己没去接触.因为之前使用gulp来作为自己的项目构建工具.现在感觉gulp使用的趋势在减少.现在这段时间去接触了webpack, ...
 - centos下-MariaDB的安装
			
安装命令: yum install mariadb mariadb-server 服务命令: systemctl start|stop|restart mariadb root用户密码设置 mysql ...
 - Java大数据应用领域及就业方向
			
最难毕业季,2017高校毕业生达到795万,许多学生面临着毕业即失业的尴尬.面对着与日俱增的竞争形势和就业压力,很多毕业生选择去知了堂学习社区镀金,以提高自己的就业竞争力,其中Java大数据是学生选择 ...
 - 【SignalR学习系列】5. SignalR WPF程序
			
首先创建 WPF Server 端,新建一个 WPF 项目 安装 Nuget 包 替换 MainWindows 的Xaml代码 <Window x:Class="WPFServer.M ...
 - 【HTML】section
			
1. 定义 标签定义文档中的节(section.区段).比如章节.页眉.页脚或文档中的其他部分. 2. div.section . article的区别 div: 本身没有任何语义,用作布局以及样式 ...