本章讨论bash shell的循环命令for、while和until
13.1 for命令
重复执行一系列命令在编程中很常见。
bash shell提供了for命令,允许你创建一个遍历一系列值的循环。每次迭代都使用其中一个值来执行已定义好的一组命令。下面是基本格式
for var in list
do
command
done
在list参数中需要提供迭代中要用到的一系列值。会依次迭代下去。每次迭代中,var会包含列表中要用到的一系列值。
do 和 done直接输入的命令可以是一条或多条标准的bash shell命令。
13.1.1 读取列表中的值
每次for命令遍历值列表,它都会将列表中的下一个值赋给$var变量。最后一次迭代后,$var变量的值会在shell脚本中剩余部分一直保持有效。(除非你修改了它)
13.1.2 读取列表中的复杂值
列表值的单引号是个大麻烦。
有两个方法可以解决
1)使用转义字符\。将单引号转义
2)使用双引号来定义用到单引号的值
在某个值两边使用双引号时,shell并不会将双引号当成值的一部分
13.1.3 从变量读取列表
将一系列的值都集中存储在了一个变量中,然后需要遍历变量中的整个列表。
#!/bin/bash
# for test
name1="xcx1 xcx2 xcx3"
name2=$name1" xcx4"
#for name in xc\'y "xcy'1 haha" xcy2 xcy3 xcy4 xcy5
for name in $name2
do
echo "Hi, My name is $name"
done
echo "The last people is $name"
name2包含了用于迭代的标准文本值列表。注意。name2用了另一个复制语句向name2变量包含的以有列表中添(或者说拼接)加了一个值。
13.1.4 从命令读取值
生成列表中所需值的另外一个途径就是使用命令的输出。
可以用命令替换来执行任何能产生输出的命令,然后在for命令中使用该命令的输出。
例子:
新建一个文件states,内容如下:
再建一个test2
#!/bin/bash
file="states"
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
运行就好了。
for仍然以每次一行的方式遍历的cat命令输出的结果。
13.1.5 更改字段分隔符
1.特殊环境变量IFS:内部字段分割符。定义了bash shell用作字段分隔符的一系列字符。
2.默认情况下会将下列字符当做字段分隔符。1)空格 2)制表符 3)换行符
3. 如果bash shell 在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。
在处理包含空格的数据时会比较麻烦。所以需要修改IFS的值。
只识别换行符,就需要这么做:IFS=$’\n’。将这个语句假如脚本中,告诉bash shell在数据值中忽略空格和制表符。
#!/bin/bash
file="states"
IFS=$’\n’
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
还有一些绝妙用法:假如需要遍历一个文件中用冒号分割的值。就可以IFS=:
如果需要指定多个字符,只需要将它们在赋值行中串起来就行。IFS=$’\n’:;” 将换行符、冒号、分号、双引号作为字段分隔符
13.1.6 用通配符读取目录
可以用for命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中使用通配符。
它会强制使用文件扩展匹配(生成匹配指定通配符的文件名或路径名的过程)。
比如下面的例子:
1 #!/bin/bash
2 for file in /home/xcy/shell/*
3 do
4 if [ -d "$file" ] # 加双引号为了解决文件名含有空格的问题
5 then
6 echo "$file is directory"
7 elif [ -f "$file" ] # 如果文件名有空格,没有双引号就会出错
8 then
9 echo "$file is file"
10 fi
11 done
for语句首先使用了文件扩展匹配来遍历通配符生成的文件列表,然后会遍历列表中的下一个文件。可以将任意多的通配符放进列表中。
13.2 C语言风格的for命令
13.2.1 C语言的for命令
以下是bash中C语言风格的for循环的基本格式:
for (( variable assignment ; condition ; interation process ))
例子:
for (( a = 1; a < 10; a++ ))
(1)变量赋值可以有空格
(2)条件中的变量不以美元符开头
(3)迭代过程的算式没有用expr命令格式。
例子:
1 #!/bin/bash
2 # C for test
3 for (( i = 10; i > 0; i-- ))
4 do
5 echo "For Test: i = $i"
6 done
13.2.2 使用多个变量
C语言风格的for命令允许为迭代使用多个变量。循环会单独处理每个变量,可以为每个变量定义不同的迭代过程。
尽管可以使用多个变量,但你只能在for循环中定义一种条件。
例子:
1 #!/bin/bash
2 # C for multiple variables test
3 for (( i=10, b = 0; i > 0; i--, b++ ))
4 do
5 echo "For Test: i = $i, b = $b"
6 done
13.3 while命令
某种意义是if-then和for循环的混杂体。
while命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的退出状态码0.它会在每次迭代的一开始测试test命令。在test命令返回非0退出状态码时,while会停止执行那组命令。(test返回0,就接着迭代,否则暂停)
13.3.1 while的基本格式
while test command
do
other commands
done
关键在于test command的退出状态码要随着循环中运行的命令而改变。否则就会停不下来
例子:用方括号检查循环命令中用的shell的变量的值
1 #!/bin/bash
2 i=10
3 while [ $i -gt 5 ] # 相当于 >
4 do
5 echo "i = $i"
6 i=$[ $i - 1 ] # 不能用i--
7 done
13.3.2 使用多个测试命令
可以在while后面接多个测试命令,只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。
例子:
1 #!/bin/bash
2 # multicommand test
3 var=2
4 while echo "var = $var"
5 [ $var -ge 0 ] # -ge 相当于大于等于 >=
6 do
7 echo "this is inside the loop"
8 var=$[ $var - 1 ]
9 done
结果:
说明每次迭代中所有的命令都会执行,包括测试命令失败的最后一次迭代。
另外,如何指定多个测试命令。每个测试命令都出现再单独的一行上。
13.4 until命令
和while相反。until命令要求你指定一个通常返回非0退出状态码的测试命令。
只有测试命令退出状态码不为0,bash shell才会执行循环中列出的命令。
一旦返回了退出状态码0,循环就结束了。
格式:
until test commands
do
other commands
done
例子:
1 #!/bin/bash
2 # until test
3 var=100
4 until [ $var -lt 0 ] # 满足条件则结束,不满足则进循环
5 # -eq ==
6 # -ge >=
7 # -lt <
8 do
9 echo "until test: var = $var"
10 var=$[ $var - 25 ]
11 done
也可以执行多个测试命令,只在最后一个成立时停止。
13.5嵌套循环
循环语句可以在循环内使用任意类型的命令,包括其他循环命令。
注意在循环嵌套时执行次数是两次循环次数相乘。
例子:
1 #!/bin/bash
2 var1=3
3 for (( var1=3; var1>0; var1-- ))
4 do
5 echo "for: var1 = $var1"
6 var2=3
7 while [ $var2 -gt 0 ]
8 do
9 echo " while: var2 = $var2"
10 var2=$[ $var2 - 1 ]
11
12 var3=3
13 until [ $var3 -eq 0 ]
14 do
15 echo " until: var3 = $var3"
16 var3=$[ $var3 - 1 ]
17 done
18 done
19 done
13.6循环处理文件数据
通常需要遍历存储在文件中的数据,需要结合两种技术:
1)使用嵌套循环
2)修改IFS环境变量
例子:
1 #!/bin/bash
2 # changing the IFS value
3 IFS.OLD=$IFS
4 IFS=$'\n' # 分隔符变为换行符
5 for entry in $(cat /etc/passwd)
6 do
7 echo "Values in [$entry]"
8 IFS=: # 分隔符变为冒号
9 for value in $entry
10 do
11 echo " $value"
12 done
13 done
外循环解析一行一行的用户信息。内循环通过冒号分割,解析一个用户的具体信息。
13.7 控制循环
有两个命令可以控制循环内部的情况:
1)break 2)continue
13.7.1 break命令
退出循环的一种简单方法。可以退出任意类型的循环,包括while和until。
下面几种情况可以使用break命令。
1.跳出单个循环
执行break时,它会尝试跳出当前正在执行的循环。
1 #!/bin/bash
2 for var in 10 9 8 7 6 5 4 3 2 1
3 do
4 if [ $var -eq 5 ]
5 then
6 echo "this is exec break;"
7 break
8 fi
9 echo "var = $var"
10 done
这个方法也适用于while和until循环。
2.跳出内部循环
处理多个循环时,break会自动终止你所在的最内层的循环。
内层循环终止了,外层循环依然会继续执行。
3.跳出外部循环
有时你在内部循环,但需要停止外部循环。break命令接受单个命令行参数。
break n
n指定了要跳出的循环层级。默认情况下n为1.表示跳出当前循环。
若为2,就表示跳出上一级的外部循环。
例子:
1 #!/bin/bash
2 for(( i=5; i>0; i-- ))
3 do
4 echo "outer loop: i = $i"
5 for(( j = 0; j < 100; j++ ))
6 do
7 echo "inside loop: j = $j"
8 if [ $j -eq 5 ]
9 then
10 break 2 # 跳出上一级循环
11 # break # 跳出当前循环
12 fi
13 done
14 done
13.7.2 continue命令
提前终止某次循环中的命令,不会完全终止整个循环。
例子:
1 #!/bin/bash
2 for(( i=0; i < 10; i++ ))
3 do
4 if [ $i -gt 4 ] && [ $i -lt 8 ]
5 then
6 continue
7 fi
8 echo "haha i = $i"
9 done
注意:这个会跳过剩余的命令,如果在剩余的命令中要对测试条件变量进行改变就会出问题。这里需要留个心眼。
也可以通过命令行参数指定要继续执行哪一级循环。 continue n
1 #!/bin/bash
2 for(( i=0; i < 5; i++ ))
3 do
4 echo "out loop; i = $i"
5 for(( j=0; j<4; j++ ))
6 do
7 echo " inside loop +++ j = $j"
8 if [ $j -eq 2 ]
9 then
10 continue 2 #继续上一级循环 还可以不接2,表示继续当前循环
11 fi
12 echo " inside loop --- j = $j"
13 done
14 done
注意break和continue的区别:
break用于完全结束一个循环,后面的循环也不执行了。
continue用来结束当前循环,后面的循环还会执行。
比如:
for(i = 0; i < 10; i++)
do
if [ $i –eq 5]
then
break # 6 , 7 , 8 ,9 就都不会打印了,结束了。
# continue # 仅仅不打印5
fi
echo “i = $i”
done
13.8 处理循环的输出
直接上例子吧。直接在done后面接 > xxx.txt
13.9 实例
13.9.1 查找可执行文件
找出系统中有哪些可执行文件可供使用,只找PATH环境变量中所有的目录就行了
例子:
1 #!/bin/bash
2 # find files in the PATH
3 IFS=:
4 for folder in $PATH # 将各个目录放入folder中
5 do
6 echo "$folder"
7 for file in $folder/* # 迭代指定目录中的所有文件
8 do
9 if [ -x $file ] # 检查是否有可执行权限
10 then
11 echo " $file"
12 fi
13 done
14 done
13.9.2 创建多个用户账户
让系统管理员更轻松。用脚本创建用户
1.先建立一个文本,里面放用户id和name。用逗号分隔
2. 再去读取上述文件中的信息
while IFS=',' read -r userid name
这个还是蛮有技巧的。read会自动读取读取.csv文本文件的下一行内容,不需要再写一个循环来处理。
read返回false时(就是读取完了)while就会退出,妙哉。
代码如下:
1 #!/bin/bash
2 # shell add user account
3 input="users.csv"
4 while IFS=',' read -r userid name # 读取里面的数据,IFS要设为逗号
5 do
6 echo "adding id:$userid name:$name"
7 useradd -c "$name" -m $userid
8 done < "$input"
执行需要sudo权限。
13.9.2 再删除创建的用户
代码如下:
1 #!/bin/bash
2 # xcy test, del user
3 IFS=$'\n'
4 for user in $(cat /etc/passwd)
5 do
6 # echo "$user"
7 IFS=:
8 for value in $user
9 do
10 if [[ $value == xiaochongyong* ]] # 这个*有点通配符的意思
11 then
12 echo "Userid: $value"
13 userdel $value
14 fi
15 break
16 done
17 done
注意那个break,因为/etc/passwd第一条就是userid,这里读取完userid就退出当前循环。