[Shell]-5-For循环

引言

For命令允许我们创建一个遍历一系列值的循环,每次迭代都使用其中一个值来执行已定义好的一组命令。

文章目录

0×1.For循环基本结构

For循环允许我们从一组数据中去取一个值作为当前循环的变量,去执行循环体中的命令,结构如下:

					for var in list
					do
						commands
					done

					#或和if语句一样,使用分号将do和for放在一行
					for var in list ; do
						commands
					done
					

在每次循环中,变量 var 会包含列表list中的当前值,第一次循环会使用列表中的第一个值,第二次循环使用第二个值,以此类推,直到列表中的所有值都取过一遍。

实例:

					#在本例中,for循环会使用str_x变量去循环遍历in后面的列表中的每个值,这个列表使用空格分隔每个值,关于列表中的分隔符,会在后面"内部字段分隔符"小节中介绍到,这里只需要了解空格也是内部字段分隔符中的一种即可
					root@qingsword.com:~# vim a.sh
					#!/bin/bash

					for str_x in qing qingsword qingsword.com www.qingsword.com ; do
						echo "$str_x"
					done

					#执行结果如下
					root@qingsword.com:~# bash a.sh 
					qing
					qingsword
					qingsword.com
					www.qingsword.com
					

在最后一次循环后,str_x 变量的值会在Shell脚本的剩余部分一直保持有效,它会一直保持最后一次迭代的值(除非你修改了它),本例中,str_x的值在脚本结束前,会一直存储"www.qingsword.com"这个值。

0×2.For循环读取列表会遇到的问题

在循环的列表部分包含单/双引号时,循环读取时可能会遇到一些问题,请看下面的实例:

					root@qingsword.com:~# vim b.sh
					#!/bin/bash
					#这一个list中有两个单引号,for会认为这两个双引号包含的部分是一个整体
					for str_a in I'm sorry but I don't think I know you ; do
						echo "$str_a"
					done

					root@qingsword.com:~# bash b.sh 
					Im sorry but I dont
					think
					I
					know
					you
					

当遇到这种包含成对单/双引号的列表结构时,可以使用转义字符\来忽略这些符号,例如,修改上面的脚本如下:

					root@qingsword.com:~# vim b.sh
					#!/bin/bash
					#添加转义字符
					for str_a in I\'m sorry but I don\'t think I know you ; do
						echo "$str_a"
					done

					root@qingsword.com:~# bash b.sh 
					I'm
					sorry
					but
					I
					don't
					think
					I
					know
					you
					

如果list列表的参数包含空格,但我们又想让它们作为一个整体读取,可以使用双引号或单引号包含,例如:

					root@qingsword.com:~# vim b.sh 
					root@qingsword.com:~# more b.sh 
					#!/bin/bash
					#我们想将"New York"作为一个整体被for循环读取,可以使用双/单引号包含它们
					for str_a in "New York" BeiJing ; do
						echo "$str_a"
					done

					root@qingsword.com:~# bash b.sh 
					New York
					BeiJing
					

0×3.For循环从从变量读取数据

列表不仅仅可以直接放在for循环结构中,还能存储在变量中,请看下面的实例:

					root@qingsword.com:~# vim c.sh
					root@qingsword.com:~# more c.sh 
					#!/bin/bash

					#存储一组列表数据
					str_list="aa bb cc dd ee ff"
					#可以使用这种方法来拼接数据,此时str_all包含了str_list所有数据并在末尾添加了gg与hh
					str_all=$str_list" gg hh"

					#循环从变量中取值
					for str_a in $str_all ; do
						echo $str_a
					done

					#执行结果
					root@qingsword.com:~# bash c.sh 
					aa
					bb
					cc
					dd
					ee
					ff
					gg
					hh
					

0×4.For循环从命令结果读取数据

除了变量外,在list部分可以执行一条命令,for循环会从命令执行的结果中依次取值,如下:

					#创建一个filea文件,并写入三行数据
					root@qingsword.com:~# echo aaa > filea
					root@qingsword.com:~# echo bbb >> filea
					root@qingsword.com:~# echo ccc >> filea

					root@qingsword.com:~# vim d.sh 
					#!/bin/bash

					str_file="filea"
					#使用cat读取filea文件中的内容,for循环会根据"内部字段分隔符"定义的符号去对文件内容进行分段读取,默认情况下"内部字段分隔符"包含了回车符
					for str_a in $(cat $str_file) ; do
						echo $str_a
					done
					root@qingsword.com:~# bash d.sh 
					aaa
					bbb
					ccc
					

0×5.内部字段分隔符

For循环在读取文件中的值时,不是以每行作为一个字段,而是根据内部字段分隔符IFS(internal field separator)来分隔每个字段,IFS是Bash Shell的一个环境变量,默认情况下,bash shell会将下列字符当作字段分隔符:

空格;
制表符;
换行符;

在这种情况下,如果IFS不能修改,我们在执行循环时,有时会产生我们不希望看到的结果,例如:

					#创建文件fileb,每一行两个单词之间有一个空格
					root@qingsword.com:~# echo New York > fileb
					root@qingsword.com:~# echo Qing Sword >> fileb
					root@qingsword.com:~# more fileb 
					New York
					Qing Sword

					#使用for循环读取文件fileb内容
					root@qingsword.com:~# vim e.sh
					#!/bin/bash

					str_fileb="fileb"

					if [ -f /$USER/$str_fileb ] ; then
						for str_a in $(cat $str_fileb) ; do
							echo "$str_a"
						done
					fi

					#执行结果如下,输出的结果被空格分隔了,for读取文件内容的过程中遇到空格、制表符、换行符,就会终止读取,它将这些内部字段分隔符当做一个字段读取的结束
					root@qingsword.com:~# bash e.sh 
					New
					York
					Qing
					Sword

					#解决这个问题的最好方法是,在脚本中重定义IFS变量值,让它只包含我们想要的字段分隔符
					root@qingsword.com:~# vim e.sh 
					#!/bin/bash

					str_fileb="fileb"
					#让IFS只包含换行符
					IFS=$'\n'

					if [ -f /$USER/$str_fileb ] ; then
						for str_a in $(cat $str_fileb) ; do
							echo "$str_a"
						done
					fi

					#执行结果得到了我们想要的样子,for循环仅用换行符作为每次循环读取的结束,遇到空格不会终止读取
					root@qingsword.com:~# bash e.sh 
					New York
					Qing Sword
					

在处理代码量较大的脚本时,可能在一个地方需要修改 IFS 的值,然后忽略这次修改,在脚本的其他地方继续沿用 IFS 的默认值,一个可参考的安全实践是在改变 IFS 之前保存原来的 IFS 值,之后再恢复它,这种技术可以这样实现:

					#创建一个filec和filed
					root@qingsword.com:~# echo aa:bb:cc:dd:ee:ff > filec
					root@qingsword.com:~# echo "www;qing\"sword:com" > filed

					#创建脚本
					root@qingsword.com:~# vim f.sh
					#!/bin/bash

					str_filec="filec"
					str_filed="filed"
					#存储IFS原始值,方便在需要时恢复
					IFS_BK=$IFS

					if [ -f /$USER/$str_filec ] ; then
						#最好使用转义符\,当遇到一些特殊字符时不容易出错,让for循环使用":"符号来分割文件内容中每个字段
						IFS=\:  
						for str_a in $(cat $str_filec) ; do
							echo $str_a
						done
					fi

					if [ -f /$USER/$str_filed ] ; then
					        #分号和引号必须使用转移符,不然程序会报错
					        IFS=\:\;\"  
					        for str_a in $(cat $str_filed) ; do
					                echo $str_a
					        done    
					fi 

					#执行
					root@qingsword.com:~# bash f.sh 
					aa
					bb
					cc
					dd
					ee
					ff
					www
					qing
					sword
					com
					

0×6.使用通配符遍历目录

可以在 for 命令列表部分中列出多个目录,for将依次读取列举出每个目录中的文件或文件夹,请看下面的实例:

					root@qingsword.com:~# vim g.sh
					#!/bin/bash
					#定义两个目录(在目录的末尾一定要使用通配符,这样就可以使用for来遍历目录中每个文件和文件夹了)
					str_dir_a="$HOME/*"
					str_dir_b="/*"
					 #因为目录或文件名中可能包含空格,所以需要使用双引号将$str_a括起来,否则-d判断包含空格会报错
					for str_a in $str_dir_a $str_dir_b ; do
						if [ -d "$str_a" ] ; then 
							echo "$str_a is a directory"
						elif [ -f "$str_a" ] ; then
							echo "$str_a is a file"
						fi
					done

					#执行
					root@qingsword.com:~# bash g.sh 
					/root/a.sh is a file
					/root/b.sh is a file
					.....省略部分输出
					

0×7.C语言风格的For循环

在Bash Shell中,可以使用双括号来实现C语言风格的For循环,请看下面的实例:

					root@qingsword.com:~# vim h.sh
					#!/bin/bash

					for (( i=1;i<=6;i++ )) ; do
						echo "$i"
					done

					root@qingsword.com:~# bash h.sh 
					1
					2
					3
					4
					5
					6

					#多个初始化变量
					root@qingsword.com:~# vim i.sh
					#!/bin/bash

					for (( x=1,y=6;x<=6;x++,y-- )) ; do
						echo "$x - $y"
					done
					root@qingsword.com:~# bash i.sh 
					1 - 6
					2 - 5
					3 - 4
					4 - 3
					5 - 2
					6 - 1