bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.htmlhtml
在写while循环的时候,发现了一个问题,在while循环内部对变量赋值、定义变量、数组定义等等环境,在循环外面失效。shell
一个简单的测试脚本以下:数组
#!/bin/bash
echo "abc xyz" | while read line
do
new_var=$line
done
echo new_var is null: $new_var?
执行结果证实,$new_var的结果是空值。bash
问题出在管道上。先看看下面的内容。函数
while循环的写法有好几种,它的语法结构为:工具
while test_cmd_list; do cmd_list; done测试
但更常常地,while循环更多地用于读取标准输入的内容来实现循环。有如下几种写法:spa
写法一:使用管道传递内容,这是用的最多、但却最烂的写法code
echo "abc xyz" | while read line htm
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=0 [root@xuexi ~]# cat /etc/fstab | while head -n 2 ; [[ "$i" -le 3 ]];do echo $i;let ++i;done # 0 1 2 3
使用进程替换的结果是同样的。
[root@xuexi ~]# i=0 [root@xuexi ~]# while head -n 2; [[ "$i" -le 3 ]];do echo $i;let ++i;done < <(cat /etc/fstab) # 0 1 2 3
但若是是直接将实体文件进行重定向传递给head,则结果和上面的不同。
[root@xuexi ~]# 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匹配到内容就退出,但却不作位置标记,并且数据资源使用一次就丢弃。
[root@xuexi ~]# (head -n 2;head -n 2) </etc/fstab # # /etc/fstab # Created by anaconda on Thu May 11 04:17:44 2017
[root@xuexi ~]# (sed -n /default/'{p;q}' ;sed -n /default/'{p;q}') </etc/fstab UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8 / xfs defaults 0 0
其实在实际应用过程当中,这根本就不是个问题,由于搜索和处理文本数据的工具虽然很多,但绝大多数都是用一次文本就"丢"一次,几乎不可能所以而产生问题。之因此说这么多废话,主要是想说上面的5种while写法中,使用最普遍的写法一虽然最简单、方便,但实际上是最烂的一种。