循环
13.1 for命令
1 2 3 4 |
for var in list do commands done |
当list中包含 包含单引号的单词 可采用方法:使用反斜线转义单引号 使用双引号来定义含有单引号的值
当list中包含 包含空格的值 必须将其放入双引号内,因为for命令使用空格来划分列表中的每个值
示例:
1 2 3 4 5 6 7 8 |
#!/bin/bash for test in I\'m in "New York" and "You're" in Mexico do echo "Now going on $test" done echo "Here is out of for and the value of test is $test." |
$test变量的值在shell脚本的剩余部分仍旧有效
运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
┌──(beeee㉿beeee)-[~/testdir] └─$ sudo chmod u+x test_for1 ┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_for1 Now going on I'm Now going on in Now going on New York Now going on and Now going on You're Now going on in Now going on Mexico Here is out of for and the value of test is Mexico. |
从变量中读取值列表
可以将list保存在变量中,使用for时遍历该变量中的整个值列表
便于值列表追加(拼接)新项 var=$var” value”
示例:
1 2 3 4 5 6 7 8 9 |
#!/bin/bash list="apples pears banana lemon peaches" list=$list" grapes" for fruit in $list do echo "Do you like $fruit?" done |
输出:
1 2 3 4 5 6 7 8 9 10 11 |
┌──(beeee㉿beeee)-[~/testdir] └─$ sudo chmod u+x test_for2 ┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_for2 Do you like apples? Do you like pears? Do you like banana? Do you like lemon? Do you like peaches? Do you like grapes? |
从命令中读取值列表
可以用命令替换来执行任何能产生输出的命令,生成值列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/bin/bash # reading values from a file file="states.txt" for state in $(cat $file) do echo "Visit beautiful $state" done #states.txt #Alabama #Alaska #Arizona #Colorado #Connecticut #Delaware #Florida #Georgia #New York #North Carolina |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_for3 Visit beautiful Alabama Visit beautiful Alaska Visit beautiful Arizona Visit beautiful Colorado Visit beautiful Connecticut Visit beautiful Delaware Visit beautiful Florida Visit beautiful Georgia Visit beautiful New Visit beautiful York Visit beautiful North Visit beautiful Carolina |
可见 New York 和 North Carolina 各拆分成两行输出,但这不是我们想要的结果
更改命令分隔符
IFS环境变量定义了bash shell用作字段分隔符的一系列字符。 在默认情况下,bash shell会将 空格,制表符,换行符 视为字段分隔符。
可在shell脚本中临时更改IFS环境变量的值来限制被bash shell视为字段分隔符的字符
在先前的脚本加入语句IFS=$’\n’,得到输出:
1 2 3 4 5 6 7 8 9 10 11 12 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_for4 Visit beautiful Alabama Visit beautiful Alaska Visit beautiful Arizona Visit beautiful Colorado Visit beautiful Connecticut Visit beautiful Delaware Visit beautiful Florida Visit beautiful Georgia Visit beautiful New York Visit beautiful North Carolina |
如果想修改IFS的值,使其只能识别换行符,可以将语句IFS=$'\n'
加入脚本,告诉bash shell忽略数据中的空格和制表符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/bin/bash # reading values from a file file="states.txt" IFS=$'\n' for state in $(cat $file) do echo "Visit beautiful $state" done #states.txt #Alabama #Alaska #Arizona #Colorado #Connecticut #Delaware #Florida #Georgia #New York #North Carolina |
在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后再将其恢复原状,使脚本的其他地方继续沿用IFS的默认值。可在修改IFS之前保存原来的IFS值,之后再恢复它:
1 2 3 4 |
IFS.OLD=$IFS IFS=$'\n' <在代码中使用新IFS值> IFS=$IFS.OLD |
还有一些比较常用的用法,比如:
如果要遍历文件中以冒号分隔的值,只需把IFS的值设为冒号: IFS=:
如果要指定多个IFS字符,只需在赋值语句中将这些字符写在一起: IFS=$'\n:;"'
使用通配符读取目录
可以用for命令来自动遍历目录中的文件。为此必须在文件名或路径名中使用通配符。
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/bash for file in /home/beeee/testdir/* do if [ -d "$file" ] then echo "$file is a directory." elif [ -f "$file" ] then echo "$file is a file." fi done |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_for5 /home/beeee/testdir/newfile is a file. /home/beeee/testdir/numeric_test is a file. /home/beeee/testdir/states.txt is a file. /home/beeee/testdir/test[[]] is a file. /home/beeee/testdir/test_and is a file. /home/beeee/testdir/test_ex is a file. /home/beeee/testdir/test_for1 is a file. /home/beeee/testdir/test_for2 is a file. /home/beeee/testdir/test_for3 is a file. ... |
注意:在这个例子中使用了语句if [ -d “$file” ] 因为在linux内,目录名和文件名包含空格是合法的。面对这种情况,就需要把$file放入双引号内
也可以在for命令中列出多个目录通配符,如: for file in /home/rich/.b* /home/rich/badtest
13.2 C语言风格的for命令
for (( a = 1; a < 10; a++ ))
- 变量赋值可以有空格
- 迭代条件中的变量不以$符号开头
- 迭代中的算式不使用expr命令格式
也可以使用多个变量,示例:
1 2 3 4 5 6 |
#!/bin/bash for (( a = 1, b = 10; a <= 10; a++, b-- )) do echo "$a --- $b" done |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_for6 1 --- 10 2 --- 9 3 --- 8 4 --- 7 5 --- 6 6 --- 5 7 --- 4 8 --- 3 9 --- 2 10 --- 1 |
13.3 while命令
1 2 3 4 |
while test commands do other commands done |
test command的退出状态码必须随着循环中执行的命令而改变
while命令允许多个测试命令,但只有最后一个测试命令的退出状态码会被用于决定是否结束循环
要把每个测试命令放在单独的一行中
1 2 3 4 5 6 7 8 9 |
#!/bin/bash var1=10 while echo $var1 [ $var1 -ge 0 ] do echo "This is inside the loop." var1=$[ $var1 - 1 ] done |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_while 10 This is inside the loop. 9 This is inside the loop. 8 This is inside the loop. 7 This is inside the loop. 6 This is inside the loop. 5 This is inside the loop. 4 This is inside the loop. 3 This is inside the loop. 2 This is inside the loop. 1 This is inside the loop. 0 This is inside the loop. -1 |
13.4 until命令
until命令要求指定一个返回非0退出状态码的测试命令,只要测试命令的退出状态码不为0,bash shell就会执行循环中列出的命令;当测试命令返回了退出状态码0,循环终止。
1 2 3 4 |
until test commands do other commands done |
test command的退出状态码必须随着循环中执行的命令而改变
1 2 3 4 5 6 7 |
#!/bin/bash var1=100 until [ $var1 -eq 0 ] do echo $var1 var1=$[ $var1 - 25 ] done |
输出:
1 2 3 4 5 6 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_until 100 75 50 25 |
13.5 嵌套循环
while for until循环可嵌套使用
13.6 循环处理文件数据
示例:处理/etc/passwd文件
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/bin/bash IFS.OLD=$IFS IFS=$'\n' for entry in $(cat /etc/passwd) do echo "Values in $entry -" IFS=: for value in $entry do echo " $value" done done |
外层IFS解析出文件的各行,内层IFS将IFS的值修改为冒号,已解析出/etc/passwd文件中各行的字段
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_loop ./test_loop: line 3: IFS.OLD=: command not found Values in root:x:0:0:root:/root:/bin/bash - root x 0 0 root /root /bin/bash Values in daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin - daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin |
13.7 循环控制
break
默认跳出最内层循环
通过语句 break n
可结束外层循环 在默认情况下,n为1,表明跳出的是当前循环 如果将n设为2,那么break命令就会停止下一级的外层循环
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/bin/bash for (( a = 1; a < 4; a++ )) do echo "Outer loop: $a" for (( b = 1; b < 100; b++ )) do if [ $b -gt 5 ] then break 2 fi echo " Inner loop: $b" done done |
输出:
1 2 3 4 5 6 7 8 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_break Outer loop: 1 Inner loop: 1 Inner loop: 2 Inner loop: 3 Inner loop: 4 Inner loop: 5 |
可见,当shell执行了break命令后,外部循环就结束了
continue
和break命令一样,可通过 continue n
指定n来指定要指定继续哪一级循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash for (( a = 1; a <= 5; a++ )) do echo "Iteration $a:" for (( b = 1; b < 3; b++ )) do if [ $a -gt 2 ] && [ $a -lt 4 ] then continue 2 fi var3=$[ $a * $b ] echo " The result of $a * $b is $var3" done done |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_continue Iteration 1: The result of 1 * 1 is 1 The result of 1 * 2 is 2 Iteration 2: The result of 2 * 1 is 2 The result of 2 * 2 is 4 Iteration 3: Iteration 4: The result of 4 * 1 is 4 The result of 4 * 2 is 8 Iteration 5: The result of 5 * 1 is 5 The result of 5 * 2 is 10 |
13.8 处理循环的输出
可以在done命令之后,对循环的输出使用管道或进行重定向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash for (( a = 1; a < 10; a++ )) do echo "The number is $a" done > test_out1.txt echo "The command is finished." for state in "North Dakota" Connection Illinois Alabama Tennessee do echo "$state is the next place to go" done | sort echo "This completes our travels." |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
┌──(beeee㉿beeee)-[~/testdir] └─$ ./test_loopout The command is finished. Alabama is the next place to go Connection is the next place to go Illinois is the next place to go North Dakota is the next place to go Tennessee is the next place to go This completes our travels. ┌──(beeee㉿beeee)-[~/testdir] └─$ cat test_out1.txt The number is 1 The number is 2 The number is 3 The number is 4 The number is 5 The number is 6 The number is 7 The number is 8 The number is 9 |