for 命令
当你需要重复一组命令直至达到某个特定的条件,比处如处理某个目录下的所有文件、系统上的所有用户或是某个文本文件中的所有行。
for 的基本格式
for var in list
do
commands
done
在list参数中,你提供了迭代中要用的一系列的值。你可以通过几种不同的途径来制定列表中的值。
在 do 和 done 语句之间输入的命令可以是一条或者多条标准的shell 命令,在这些命令中$var变量
包含着这次迭代对应的当前那个列表中的值。
说明: 只要你愿意,也可以将do 语句和for 语句放在同一行,但必须用分号将其同列表中的值分开: for var in list : do
for 最基本的用法就是遍历for 命令自身中定义的一系列值:
[root@zw-test-db ~]# vim test27
#!/bin/bash
# basic for command
for test in zhengwei yagnman oracle root mysql sqlser
do
echo the next state is $test
done
[root@zw-test-db ~]# sh test27
the next state is zhengwei
the next state is yagnman
the next state is oracle
the next state is root
the next state is mysql
the next state is sqlser
每次 for 命令遍历提供的值列表时,它会将列表中的下一个值赋给$test变量像for 命令语句中的任何其他脚本变量一样使用 。在最后一次迭代后,
$test变量的值会在shell脚本中的剩余部分一直保持有效。它会一直保持到最后一次迭代的值(除非你修改了它)
[root@zw-test-db ~]#vim test28
#!/bin/bash
# testing the for variable after the looping
for test in zhengwei yangman oracle mysql sqlserver
do
echo "the next state is $test"
done
echo "the last state we visited was $test"
test=mongodb
echo "wait. now we're visiting $test"
[root@zw-test-db ~]# sh test28
the next state is zhengwei
the next state is yangman
the next state is oracle
the next state is mysql
the next state is sqlserver
the last state we visited was sqlserver
wait. now we're visiting mongodb
test 变量保持了它的值,也允许我们修改它的值并在for 循环之外跟其他变量一样使用
读取列表中的复杂值
[root@zw-test-db ~]#vim test29
#!/bin/bash
#another example of how not to use the for command
for test in i don't know if this'll work
do
echo "work:$test"
done
[root@zw-test-db ~]# sh test29
work:i
work:dont know if thisll
work:work
shell 看到了列表值中的单引号并尝试使用他们来定义一个单独的数据值,整个过程是混乱的
有两个办法解决这个问题
使用转义字符(反斜线)来将单引号转义;
使用双引号来定义用到单引号的值
[root@zw-test-db ~]# vim test30
#!/bin/bash
# another example of how not to use the command
for test in I don\'t know if "this'll" work
do
echo "work:$test"
done
[root@zw-test-db ~]# sh test30
work:I
work:don't
work:know
work:if
work:this'll
work:work
在第一个有问题的值上,添加了反斜杠字符来转义don't值中的单引号,第二个有问题的值上,将this'll 用 双引号圈起来。两种方法都成正常
你可能遇到的另一个问题是有多个词的值,记住,for 循环假定每个值都是用空格分隔的。如果保护空格的数据值,你可能遇到一下问题
[root@zw-test-db ~]# vim test 31
#!/bin/bash
#another example of how not to use the for command
for test in zhu shan zhu ping shiyan hubei
do
echo "now going to $test"
done
[root@zw-test-db ~]# sh test31
now going to zhu
now going to shan
now going to zhu
now going to ping
now going to shiyan
now going to hubei
这不是我们想要的结果。for命令用空格来划分列表中的每一个值。如果在单独的数据值中有空格,那么你必须用引号来将这些值圈起来
[root@zw-test-db ~]# vim test32
#!/bin/bash
# an example of how to properly define values
for test in "zhu shan" "zhu ping" shiyan hubei
do
echo "now going to $test"
done
[root@zw-test-db ~]# sh test32
now going to zhu shan
now going to zhu ping
now going to shiyan
now going to hubei
注意:当你某个值两边有双引号时,shell不会将双引号当成值的一部分。
从变量读取列表
通常shell脚本遇到的情况是,你将一系列值都集中在了一个变量中,然后需要遍历整个列表。
[root@zw-test-db ~]#vim test33
#!/bin/bash
#using a variable to hold the list
list="zhengwei yagnman oracle mysql"
list=$list"Connecticut"
for state in $list
do
echo "have you ever visited $state?"
done
[root@zw-test-db ~]# sh test33
have you ever visited zhengwei?
have you ever visited yagnman?
have you ever visited oracle?
have you ever visited mysql?
have you ever visited Connecticut?
注意:还用了另一个赋值语句来向$list变量包含的已有的列表添加了一个值。这是向变量中存储的已有的文本字符串尾部添加文本的一个常用方法。
从命令读取值
生成列表中要用的值的另外一个途径就是使用命令的输出。你可以使用反引号
来执行任何产生输出的命令,然后在for循环中使用该命令输出
[root@zw-test-db ~]#vim test34
#!/bin/bash
#reading values from a file
file="states"
for state in `cat $file`
do
echo "visit beautiful $state"
done
[root@zw-test-db ~]# cat states
zhengwei
yangman
oracle
mysql
mongodb
[root@zw-test-db ~]# sh test34
visit beautiful zhengwei
visit beautiful yangman
visit beautiful oracle
visit beautiful mysql
visit beautiful mongodb
更改文字段分隔符
这个问题的原因是特殊的环境变量IFS,称为内部字段分隔符,
IFS环境变量定义了shell 用作字段分隔符的一系列字符串。默认情况下,
shell 会将下列字符当做字段分隔符:
空格
制表符
换行符
可以在shell脚本中临时更改IFS环境变量的值来限制一下被shell当坐字段分隔符
的字符。如果你修改的IFS的值使其只能识别换行符,你必须这么做:
IFS=$'\n'
将这个语句加入到脚本中,告诉shell在数据中忽略空格和制表符。
对当前一个脚本使用这种方法,将获得如下输出
[root@zw-test-db ~]#vim test35
#!bin/bash
# reding values from a file
file="state"
IFS=$'\n'
for state in `cate $file`
do
echo "visit beautiful $state"
done
[root@zw-test-db ~]# sh test35
visit beautiful zhengwei
visit beautiful yangman
visit beautiful oracle
visit beautiful mysql
visit beautiful mongodb
visit beautiful zhu shuan
visit beautiful zhu ping
现在shell脚本能够使用列表中含有空格的值了
警告: 在处理长脚本时,可能在一个地方需要修改IFS的值,然后忘掉它了并在脚本其他地方以为还是默认的值。一个可以参考的简单实践是在改变IFS之前保存原来的IFS值,之后再恢复它
这一这样写
IFS.OLD=$IFS
IFS=$'\n'
<use the new IFS value in code>
IFS=$IFS.OLD
这会为脚本中后面的操作保证IFS的值恢复到了默认值
还有其他一些IFS环境变量的绝妙用法,假设你要遍历一个文件中用冒号分隔的值
(比如在/etc/passwd文件中)你要做的就是将IFS的值设为冒号:
IFS=:
如果你要指定多个IFS字符,只要将它们在赋值行串起来就行:
IFS=$'\n:;"
这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。如何使用IFS字符解析数据没有任何限制
用通配符读取目录
可以用for命令来自动遍历满是文件的目录。进行操作时,必须在文件名或路经中使用通配符。
它会强制shell使用文件扩展匹配(file globbing)。文件扩展匹配是生成匹配制定的通配符的文件名
或路径名的过程。
这个特性在处理目录中文件而你不知道所有的文件名时非常好用:
[root@zw-test-db ~]# vim test36
#!/bin/bash
# iterate through all the file in a direcotry
for file in /*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
[root@zw-test-db ~]# sh test36
/bin is a directory
/boot is a directory
/dev is a directory
/proc is a directory
/rlwrap-0.30 is a directory
/rlwrap-0.30.tar is a file
/root is a directory
/sbin is a directory
/selinux is a directory
/srv is a directory
/sys is a directory
/test14 is a file
/test26 is a file
for循环比遍历/*输出结果。
注意:在这个例子中,我们用if语句里的测试处理的有些不同
if [ -d "$file" ]
在linux中,目录名和文件名是可以有空格的。要容纳这种值,你应该将$file遍历用双引号圈起来。
如果不这么做,遇到含有空格的目录或文件名时会有错误产生:
你也可以在for命令中通过列出一系列的目录通配符来将目录查找方法和列表方法合并进同一个语句:
c语言风格的for命令
[root@zw-test-db ~]#vim test37
#!/bin/bash
#testing the c-style for loop
for ((i=1; i<=10;i++))
do
echo "the next number is $i"
done
[root@zw-test-db ~]# sh test37
the next number is 1
the next number is 2
the next number is 3
the next number is 4
the next number is 5
the next number is 6
the next number is 7
the next number is 8
the next number is 9
the next number is 10
使用多个变量
[root@zw-test-db ~]#vim test38
#!/bin/bash
#multiple variables
for(( a=1, b=10; a<=10;a++,b-- ))
do
echo "$a - $b "
done
[root@zw-test-db ~]# sh test38
1 - 10
2 - 9
3 - 8
4 - 7
5 - 6
6 - 5
7 - 4
8 - 3
9 - 2
10- 1
while 命令
while 命令某种意义上是if-then语句和for循环的混杂体。while命令允许你定义一个要测试的
命令,然后循环执行一组命令,只要定义的测试命令返回的状态码0;他会在每个迭代的一开始测试
test命令。在test命令返回非0退出状态码时,while命令会停止执行那组命令
while 的基本格式
while test command
do
other commands
done
while命令的关键是,指定的test命令的退出状态码必须随着循环中运行的命令改变如果退出状态码从来不变,那while循环将会一直不停地循环
最常见的test命令的用法是,用方括号来查看循环命令中用到shell变量的值:
[root@zw-test-db ~]#vim test39
#!/bin/bash
# while command test
var1=10
while [ $var1 -gt 0 ]
do
echo $var1
var1=$[ $var1 - 1 ]
done
[root@zw-test-db ~]# sh test39
10
9
8
7
6
5
4
3
2
1
while [ $var1 -gt 0 ]
只有测试条件成立,while命令才会继续遍历执行定义好的命令。
测试条件中用到的变量必须被修改,否则你就进入了一个无限循环。
本例中,我们用shell算术来将变量值减一:
var1=$[ $var1 - 1 ]
while 循环会在测试条件不再成立时停止
使用多个测试命令
在极少数情况下,while命令允许你在while语句行定义多个测试命令。
只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。
[root@zw-test-db ~]#vim test40
#!/bin/bash
# testing a multicommand while loop
var1=10
while echo $var1
[ $var1 -ge 0 ]
do
echo "this is inside the loop"
var1=$[ $var1 - 1 ]
done
[root@zw-test-db ~]# sh test40
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
while语句中定义了两个测试命令
while echo $var1
[ $var1 -ge 0 ]
第一个测试简单的显示var1变量的当前值,
第二个方括号来决定变量的值,在循环内部,echo 语句会显示一条简单的消息说明循环被执行了。
until 命令
until命令和while命令工作方式完全相反,until命令要求你指定一个通常输出非0退出状态码的测试命令。
只有测试退出状态码非0,shell才会指定循环中列出的那些命令。一旦测试命令返回状态码是0,循环就结束了
until命令格式如下
until test commands
do
other commands
done
类似与while命令,你可以在until命令语句中有多个测试命令,只有最后一个退出状态码决定shell是否执行定义好的其他命令
vim test41
#!/bin/bash
# using the until command
var1=100
until [ $var1 -eq 0 ]
do
echo var1
var1=$[ $var1-25 ]
done
[root@zw-test-db ~]# sh test41
100
75
50
25
until 使用多个测试命令是要注意
vim test42
#!/bin/bash
#using the until command
var1=100
until echo $var1
[ $var1 -eq 0 ]
do
echo inside the loop:$var1
var1=$[ $var1 - 25 ]
done
[root@zw-test-db ~]# sh test42
100
inside the loop:100
75
inside the loop:75
50
inside the loop:50
25
inside the loop:25
0
+6
3
嵌套循环
循环语句可以在循环内使用任意类型的命令,包括其他循环命令。 这种称为嵌套循环。
注意,在使用嵌套循环时,你是在迭代中使用迭代,命令行的次数是乘积关系。
vim test43
#!/bin/bash
# nesting for loops
for (( a=1; a<=3; a++ ))
do
echo "starting loop $a"
for (( b=1; b<=3; b++ ))
do
echo " inside loop :$b"
done
[root@zw-test-db ~]# sh test43
starting loop 1
inside loop :1
inside loop :2
inside loop :3
starting loop 2
inside loop :1
inside loop :2
inside loop :3
starting loop 3
inside loop :1
inside loop :2
inside loop :3
在混用循环命令时也一样,比如在while循环内部纺织一个for循环
[root@zw-test-db ~]#vim test44
#!/bin/bash
# placing a for loop inside a while loop
var1=5
while [ $var1 -ge 0 ]
do
echo "outer loop:$var1"
for (( var2=1; var2<3; var2++ ))
do
var3=$[ $var1 * $var2 ]
echo " Inner loop: $var1 * $var2 = $var3"
done
var1=$[ $var1 - 1 ]
done
[root@zw-test-db ~]# sh test44
outer loop:5
Inner loop: 5 * 1 = 5
Inner loop: 5 * 2 = 10
outer loop:4
Inner loop: 4 * 1 = 4
Inner loop: 4 * 2 = 8
outer loop:3
Inner loop: 3 * 1 = 3
Inner loop: 3 * 2 = 6
outer loop:2
Inner loop: 2 * 1 = 2
Inner loop: 2 * 2 = 4
outer loop:1
Inner loop: 1 * 1 = 1
Inner loop: 1 * 2 = 2
outer loop:0
Inner loop: 0 * 1 = 0
Inner loop: 0 * 2 = 0
shell 能够区分开内部的for循环的do和done命令和外部while循环的do和done命令。甚至可以混用 until 和 while 循环
[root@zw-test-db ~]# test45
#!/bin/bash
# using until and while loops
var1=3
until [ $var1 -eq 0 ]
do
echo "outer loop: $var1"
var2=1
while [ $var2 -lt 5 ]
do
var3=`echo "scale=4; $var1 / $var2" |bc`
echo " inner loop: $var1 / $var2 = $var3"
var2=$[ $var2 + 1 ]
done
var1=$[ $var1 - 1 ]
done
[root@zw-test-db ~]# sh test45
outer loop: 3
inner loop: 3 / 1 = 3.0000
inner loop: 3 / 2 = 1.5000
inner loop: 3 / 3 = 1.0000
inner loop: 3 / 4 = .7500
outer loop: 2
inner loop: 2 / 1 = 2.0000
inner loop: 2 / 2 = 1.0000
inner loop: 2 / 3 = .6666
inner loop: 2 / 4 = .5000
outer loop: 1
inner loop: 1 / 1 = 1.0000
inner loop: 1 / 2 = .5000
inner loop: 1 / 3 = .3333
inner loop: 1 / 4 = .2500
外部的until循环以值3开始并继续执行直到等于0.内部while循环会以值1开始并执行下去,
只要值小于5.每个循环都必须改变在测试条件中用到的值,否则循环就会无休止的进行下去。
循环处理文件数据
通常你必须遍历存储在文件中的数据。
使用嵌套循环;
修改IFS环境变量
通过修改IFS环境变量,就能强制for命令将文件中的每一行都当成一个条目来处理,
即便数据中有空格也是如此。一旦你从文件中提取了单独行,你可能需要再次循环来
取其中的数据。
经典的例子是处理 /etc/passwd文件中的数据。这要求逐行遍历/etc/passwd文件,并将
IFS变量的值改成冒号,这样你就能分隔开每行中的各个单独的数据段了。
[root@zw-test-db ~]#vim test46
#!/bin/bash
#changing the IFS value
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
[root@zw-test-db ~]# sh test46
values in root:x:0:0:root:/root:/bin/bash -
root
x
0
0
root
/root
/bin/bash
values in bin:x:1:1:bin:/bin:/sbin/nologin -
bin
x
1
1
bin
/bin
/sbin/nologin
values in daemon:x:2:2:daemon:/sbin:/sbin/nologin -
daemon
x
2
2
daemon
/sbin
/sbin/nologin
values in adm:x:3:4:adm:/var/adm:/sbin/nologin -
外层循环处理一行的值,内层循环处理每行的单读个值
控制循环
有几个命令可以控制循环内部的情况:
break 命令
continue命令
break 命令
break 命令是退出进行中的循环的一个简单的方法。你可以使用break命令来退出
任意类型的循环,包括while 和until循环
1. 跳出单个循环
在shell执行break命令时,它会跳出正在处理的循环:
[root@zw-test-db ~]#vim test47
#!/bin/bash
# breaking out of a for loop
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if
[ $var1 -eq 5 ]
then
breal
fi
echo "Iteration number: $var1"
done
echo "the for loop is completed"
[root@zw-test-db ~]# sh test47
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
the for loop is completed
for 循环通常都会遍历一遍列表中指定的所有值,当满足if-then的条件时,shell会执行break命令,for循环停止这种方法同样适用于while和until循环
[root@zw-test-db ~]# vim test48
#!/bin/bash
# breaking out of a while loop
var1=1
while [ $var1 -lt 10 ]
do
if [ $var1 -eq 5 ]
then
break
fi
echo "iteration : $var1"
var1=$[ $var1 + 1 ]
done
echo "the while loop is completed"
[root@zw-test-db ~]# sh test48
iteration : 1
iteration : 2
iteration : 3
iteration : 4
the while loop is completed
while 循环会在if-then 的条件满足时执行break命令终止
2.跳出内部循环
在处理多个循环时,break命令会自动终止你所在最里面的循环:
[root@zw-test-db ~]# vim test49
#!/bin/bash
# breaking out of an inner loop
for (( a=1; a<4; a++ ))
do
echo "Outer loop:$a"
for (( b=1; b<100; b++ ))
do
if [ $b -eq 5 ]
then
break
fi
echo "inner loopp:$b"
done
done
[root@zw-test-db ~]# sh test49
Outer loop:1
inner loopp:1
inner loopp:2
inner loopp:3
inner loopp:4
Outer loop:2
inner loopp:1
inner loopp:2
inner loopp:3
inner loopp:4
Outer loop:3
inner loopp:1
inner loopp:2
inner loopp:3
inner loopp:4
内部循环里的for语句指明一直重复直到变量b等于100.但内部循环的if-then语句指明当变量b的值等于5
时执行break命令。注意,即使内部循环通过break命令终止了,外部循环依然按指定的继续执行。
3.跳出外部循环
有时候你在内部循环,但需要停止外部循环。break命令接受单个命令行参数值:
break n
其中n说明了要跳出的循环层级。默认情况下,n为1,表明跳出的是当前的循环。如果设置为2,
break 命令就会停止下一级的外部循环:
[root@zw-test-db ~]# vim test50
#!/bin/bash
# breaking out of an outer loop
for (( a=1; a<4; a++))
do
echo "outer loop:$a"
for (( b=1; b<100; b++ ))
do
if [ $b -gt 4 ]
then
break 2
fi
echo " Inner loop:$b"
done
done
[root@zw-test-db ~]# sh test50
outer loop:1
Inner loop:1
Inner loop:2
Inner loop:3
Inner loop:4
当shell执行了break命令时,外部循环就停止了!
continue命令
continue 命令是提早结束执行循环内部的命令但并部完整终止于整个循环的一个途径。
它允许你在循环内部设置shell不执行命令的条件。这里有个for循环使用continue命令
简单的例子:
[root@zw-test-db ~]# vim test51
#!/bin/bash
# using the continue command
for (( var1 =1; var1 < 15; var1 ++ ))
do
if [ $var1 -gt 5 ]&&[ $var1 -lt 10 ]
then
continue
fi
echo "Iteration number: $var1"
done
[root@zw-test-db ~]# sh test51
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
Iteration number: 5
Iteration number: 10
Iteration number: 11
Iteration number: 12
Iteration number: 13
Iteration number: 14
也可以在while和until循环中使用continue命令,但要特别小心。记住当shell执行continue命令时,它会跳过剩余的命令。如果在这些条件中的某条件中增加测试条件变量,问题就出现了
vim test52
#!/bin/bash
# improperly using the continue command in a while loop
var1=0
while echo "while iteration :$var1"
[ $var1 -lt 15 ]
do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "inside iteration number:$var1"
var1=$[ $var1 + 1 ]
done
[root@zw-test-db ~]# sh test52|more
while iteration :0
inside iteration number:0
while iteration :1
inside iteration number:1
while iteration :2
inside iteration number:2
while iteration :3
inside iteration number:3
while iteration :4
inside iteration number:4
while iteration :5
inside iteration number:5
while iteration :6
while iteration :6
while iteration :6
while iteration :6
while iteration :6
while iteration :6
while iteration :6
while iteration :6
while iteration :6
和 break命令一样continue命令也允许通过命令行参数指定要哪级循环
continue n
其中n定义了要继续的循环层级。
[root@zw-test-db ~]#vim test53
#!/bin/bash
# continue an other loop
for (( a = 1; a<= 5; a++ ))
do
echo "Iterator $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
[root@zw-test-db ~]# sh test53
Iterator 1:
the result of 1 * 1 is 1
the result of 1 * 2 is 2
Iterator 2:
the result of 2 * 1 is 2
the result of 2 * 2 is 4
Iterator 3:
Iterator 4:
the result of 4 * 1 is 4
the result of 4 * 2 is 8
Iterator 5:
the result of 5 * 1 is 5
the result of 5 * 2 is 10
continue 命令来停止处理循环内的命令但继续处理外部循环。
处理循环输出
shell脚本中,你要么管接要么重定向循环的输出。你可以在done命令后面添加一个处理命令:
for file in /home/aa.txt
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif
echo "$file is a file"
fi
done > output.txt
shell 会将for命令的结果重定向到文件output.txt 中,而不是显示在屏幕上。
[root@zw-test-db ~]# vim test54
#!/bin/bash
#redirecting then for output to a file
for (( a=1; a<10; a++ ))
do
echo "the number is $a"
done >log.log
echo "the command is finished."
[root@zw-test-db ~]# sh test54
the command is finished.
[root@zw-test-db ~]# cat log.log
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
可以看到输出到log.log文件里面了
这种方法同样适用于将循环的结果管接给另一个命令
vim test55
#!/bin/bash
# piping a loop to another command
for state in "North Dakota" connecticut illinois Alabama Tennessee
do
echo "$state is the next place to go "
done |sort
echo "this completes our travels"
[root@zw-test-db ~]# sh test55
Alabama is the next place to go
connecticut 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
state 值并没有在for命令列中以特定的次序列出。for命令的输出传给了sort命令,它会改变for命令输出的结果的顺序。