文章目录
- 五、流程控制之循环
- 5.1 步进循环语句for
- 5.1.1 带列表的for循环语句
- 5.1.2 不带列表的for循环语句
- 5.1.3 类C风格的for循环语句
- 5.2 while循环语句
- 5.2.1 while循环读取文件
- 5.2.2 while循环语句示例
- 5.3 until循环语句
- 5.4 select循环语句
- 5.5 嵌套循环
- 5.4 利用break和continue语句控制循环
- 练习
五、流程控制之循环
5.1 步进循环语句for
for循环是最简单,也是最常用的循环语句。与其他的程序设计语言一样,for循环都是初学者在学习循环结构时的入门课程。for循环通常用于遍历整个对象或者数字列表。按照循环条件的不同,for循环语句可以分为带列表的for循环、不带列表的for循环以及类C风格的for循环。
5.1.1 带列表的for循环语句
带列表的for循环通常用于将一组语句执行已知的次数,其基本语法如下:
for variable in list
do
statement1
statement2
...
done
在上面的语法中,variable称为循环变量,list是一个列表,可以是一系列的数字或者字符串,元素之间使用空格隔开。do和done之间的所有的语句称为循环体,即循环结构中重复执行的语句。for循环体的执行次数与list中元素的个数有关。在带列表的for语句执行时,Shell会将in关键字后面的list列表的第1个元素的值赋给变量variable,然后执行循环体;当循环体中的语句执行完毕之后,Shell会将列表中的第2个元素的值赋给变量variable,然后再次执行循环体。当list列表中的所有的元素都被访问后,for循环结构终止,程序将继续执行done语句后面的其他的语句。
示例1:直接列出变量列表所有元素
方法1:直接列出元素方法
[root@localhost test5]# vim list.sh
#!/bin/bash
for IP in 192.168.1.101 192.168.1.102
do
echo $IP
done
[root@localhost test5]# chmod +rx list.sh
[root@localhost test5]# ./list.sh
192.168.1.101
192.168.1.102
方法2:使用大括号
[root@localhost test5]# vim list2.sh
#!/bin/sh
for IP in 192.168.1.10{1..5}
do
echo $IP
done
[root@localhost test5]# chmod +x list2.sh
[root@localhost test5]# ./list2.sh
192.168.1.101
192.168.1.102
192.168.1.103
192.168.1.104
192.168.1.105
方法3:使用seq
[root@localhost test5]# vim list3.sh
for IP in $(seq -f "192.168.1.10%1g" 1 5)
do
echo $IP
done
[root@localhost test5]# chmod +x list3.sh
[root@localhost test5]# ./list3.sh
192.168.1.101
192.168.1.102
192.168.1.103
192.168.1.104
192.168.1.105
说明:seq的用法如下
[root@localhost test5]# seq -f 'hello%03g' 1 2
hello001
hello002
示例2:获取当前目录下的普通文件的文件名作为变量列表打印输出
[root@localhost test5]# vim list4.sh
#!/bin/sh
for FILE in $(ls -F | grep -v /$)
do
echo $FILE
done
[root@localhost test5]# chmod +x list4.sh
[root@localhost test5]# ./list4.sh
list2.sh
list3.sh
list4.sh
list.sh
说明:
[root@localhost test5]# ls -F / | grep -v /$
bin@
lib@
lib64@
sbin@
示例3:打印出下面语句中字符数不大于6的单词
rabbit is favorite to eat cabbage
[root@localhost test5]# cat 3.sh
for n in rabbit is favorite to eat cabbage
do
if [ `expr length $n` -le 6 ]
then
echo $n
fi
done
[root@localhost test4]# ./3.sh
rabbit
is
to
eat
Shell允许用户指定for语句的步长。当用户需要另外指定步长时,其基本语法如下:
for varibale in {start..end..step}
do
statement1
statement2
...
done
示例:通过for循环,计算100以内奇数的和
[root@localhost test5]# cat 3.sh
#!/bin/bash
sum=0;
for i in {1..100..2}
do
let "sum+=i"
done
echo "the sum is $sum"
[root@localhost test5]# ./3.sh
the sum is 2500
5.1.2 不带列表的for循环语句
在某些特殊情况下,for循环的条件列表可以完全省略,称为不带列表的for循环语句。如果没有为for循环提供条件列表,Shell将从命令行获取条件列表。不带列表的for循环语句的一般语法如下:
for variable
do
statement1
statement2
...
done
由于系统变量$@同样可以获取所有的参数,所以以上的语法等价于以下语法:
for variable in $@或$*
do
statement1
statement2
...
done
示例:
[root@localhost test5]# vim 1.sh
#!/bin/bash
for arg
do
echo $arg
done
[root@localhost test5]# bash 1.sh {1..6}
1
2
3
4
5
6
5.1.3 类C风格的for循环语句
for ((expression1;expression2;expression3))
do
statement1;
statement2;
...
done
在上面的语法中,for循环语句的执行条件被2个圆括号包括起来。执行条件分为3个部分,由2个分号隔开,第1部分expression1通常是条件变量初始化的语句;第2部分expression2是决定是否执行for循环的条件。当expression2的值为0时,执行整个循环体;当expression2的值为非0时,退出for循环体。
第3部分,即表达式expression3通常用来改变条件变量的值,例如递增或者递减等。
示例1:批量创建用户:
- 用户名以test开头,按数字序号变化;
- 一共添加30个账号,即test01,tes02…,test30
- 用户初始密码为123456
分析:
4. 根据要求使用循环语句完成
5. 创建用户使用useradd命令,根据命名规则,
6. 10以下的需要加0 初始账号设置方法(echo “123456” | passwd –stdin username)
解答:
[root@localhost test5]# vim add_user.sh
#!/bin/bash
for ((i=1;i<=30;i++))
do
if [ $i -lt 10 ]
then
user=test0$i
else
user=test$i
fi
if ! id -u $user &> /dev/null
then
useradd $user
echo "123456" | passwd --stdin $user &> /dev/null
else
echo "$user is exists..."
fi
done
注意:可以简化写法,直接用for带列表的循环,这样就不用for里面嵌套if判断
比如for i in {01..30}
示例2:编写一个 Shell 程序,实现判断当前网络(假定为192.168.1.0/24,根据实际情况实现)里,当前在线用户的IP有哪些。
[root@localhost test5]# vim online_user.sh
#!/bin/bash
for ((i=1;i<=254;i++))
do
if ping -c 2 192.168.1.$i &>/dev/null
then
echo "192.168.1.$i is up..."
else
echo "192.168.1.$i is down..."
fi
done
5.2 while循环语句
while循环是另外一种常见的循环结构。使用while循环结构,可以使得用户重复执行一系列的操作,直到某个条件的发生。这听起来好像跟until循环非常相似,但是与until语句相比,while语句有着较大的区别。
while循环语句的基本语法如下:
while expression
do
statement1
statement2
...
done
在上面的语法中,expression表示while循环体执行时需要满足的条件。虽然可以使用任意合法的Shell命令,但是,通常情况下,expression代表一个测试表达式,当expression的值为0时才执行循环体中的语句,每次执行到done时就会重新判断while条件表达式是否成立,当expression的值为非0值时,将退出循环体。与其他的循环结构一样,do和done这2个关键字之间的语句构成了循环体。
5.2.1 while循环读取文件
[root@localhost test5]# vim online_user.sh
#!/bin/bash
for ((i=1;i<=254;i++))
do
if ping -c 2 192.168.1.$i &>/dev/null
then
echo "192.168.1.$i is up..."
else
echo "192.168.1.$i is down..."
fi
done
while expression
do
statement1
statement2
...
done
方法一:采用exc读取文件,然后进入while循环处理
[root@localhost test5]# cat file
shuju1
shuju2
shuju3
[root@localhost test5]# cat file.sh
#!/bin/bash
exec < file
while read a
do
echo $a
done
[root@localhost test5]# bash file.sh
shuju1
shuju2
shuju3
方法二:使用cat读文件,然后通过管道进入while循环处理
cat file | while read line
do
statement1
done
方法三:通过在while循环结尾,使用输入重定向方式
while read line
do
statement1
done < File
5.2.2 while循环语句示例
示例1:猜商品价格
- 通过变量RANDOM获得随机数价格
- 提示用户猜测并记录次数,猜中后退出循环
分析:
- 产生随机数价格
- 猜中退出循环,显然要用死循环(while true),满足条件退出程序
- 通过if条件判断,根据用户输入数值和生成随机数比较
- 记录猜测次数,每猜测一次加1
解答:
[root@localhost test5]# vim price.sh
#!/bin/bash
PRICE=$[$RANDOM % 100 ]
TIMES=0
while true
do
read -p "Please enter the product price [0-99] : " INT
let TIMES++
if [ $INT -eq $PRICE ]
then
echo "Good luck,you guessed it."
echo "You have guessed $TIMES times."
exit 0
elif [ $INT -gt $PRICE ]
then
echo "$INT is too high"
else
echo "$INT is too low"
fi
done
示例2:while读取文件
工作过程中遇到要从一个ip列表中获取ip port,然后ssh ip 到目标机器进行特定的操作
[root@localhost test5]# cat iplist
192.168.147.101 5225
192.168.147.102 2234
192.168.147.103 4922
[root@localhost test5]# vim ssh_port.sh
#!/bin/bash
while read line
do
IP=$(echo $line |awk '{print $1}')
PORT=$(echo $line |awk '{print $2}')
echo "IP: $IP, PORT: $PORT"
done <iplist
注意:
[root@rhel7 test6]# cat iplist
192.168.168.16 22
192.168.168.146 22
192.168.168.128 22
[root@rhel7 test6]# cat test1.sh
#!/bin/bash
while read line
do
IP=$(echo $line |awk '{print $1}')
PORT=$(echo $line |awk '{print $2}')
echo "IP: $IP, PORT: $PORT"
ssh -p $PORT root@$IP
done < iplist
[root@rhel7 test6]# bash test1.sh
IP: 192.168.168.16, PORT: 22
Pseudo-terminal will not be allocated because stdin is not a terminal.
ssh: connect to host 192.168.168.16 port 22: No route to host
IP: 192.168.168.146, PORT: 22
Pseudo-terminal will not be allocated because stdin is not a terminal.
Activate the web console with: systemctl enable --now cockpit.socket
bash: 192.168.168.128: command not found
如果在while循环中调用了ssh命令,那么ssh就会把当前输入中所有的数据读走,也就是重定向给while命令
的数据,都被ssh命令读走了,以至于下次循环的时候,read读到的内容为空,导致循环提前结束。
示例3:将之前用for语句创建的test01-test30用户删除
[root@localhost test5]# vim del_user.sh
#!/bin/bash
i=1
while [ $i -le 30 ]
do
if [ $i -le 9 ]
then
user=test0$i
else
user=test$i
fi
if id -u $user &>/dev/null
then
userdel -r $user
else
echo "$user is not exists..."
fi
let i++
done
注意:while判断变量也可以使用(()),比如while ((i<=30))
5.3 until循环语句
until循环语句同样也存在于多种程序设计语言中。顾名思义,until语句的作用是将循环体重复执行,直到某个条件成立为止。until语句的基本语法如下:
until expression
do
statement1
statement2
...
done
在上面的语法中,expression是一个条件表达式。当该表达式的值不为0时,将执行do和done之间的语句;当expression的值为0时,将退出until循环结构,继续执行done语句后面的其它的语句。
示例:将之前用for语句创建的test01-test30用户删除
[root@localhost test5]# vim del_user1.sh
#!/bin/bash
i=1
until [ $i -gt 30 ]
do
if [ $i -le 9 ]
then
user=test0$i
else
user=test$i
fi
if id -u $user &>/dev/null
then
userdel -r $user
else
echo "$user is not exists..."
fi
let i++
done
5.4 select循环语句
select循环语句的主要功能是创建菜单,在执行带有select循环语句脚本时,输出会按照数字顺序的列表显示一个菜单,并显示提示符(默认是#?),同时等待用户输入数字选择。select语句的基本语法如下:
select 变量名 [ in 菜单值列表 ]
do
statement1
statement2
...
done
示例:
[root@localhost test5]# vim select.sh
#!/bin/bash
select mysql_version in 5.1 5.6
do
echo $mysql_version
done
[root@localhost test5]# bash select.sh
1) 5.1
2) 5.6
#? 1
5.1
#? 2
5.6
#?
5.5 嵌套循环
在程序设计语言中,嵌套的循环也是一种非常常见的结构。Shell同样也支持嵌套循环。通过嵌套循环,可以完成更复杂的功能。
示例1:打印九九乘法表
[root@localhost test5]# vim table.sh
#!/bin/bash
for i in `seq 9`
do
for j in `seq 9`
do
[ $j -le $i ] && echo -n "$i*$j= `echo $(($i*$j))` "
done
echo " "
done
[root@localhost test5]# bash table.sh
1*1= 1
2*1= 2 2*2= 4
3*1= 3 3*2= 6 3*3= 9
4*1= 4 4*2= 8 4*3= 12 4*4= 16
5*1= 5 5*2= 10 5*3= 15 5*4= 20 5*5= 25
6*1= 6 6*2= 12 6*3= 18 6*4= 24 6*5= 30 6*6= 36
7*1= 7 7*2= 14 7*3= 21 7*4= 28 7*5= 35 7*6= 42 7*7= 49
8*1= 8 8*2= 16 8*3= 24 8*4= 32 8*5= 40 8*6= 48 8*7= 56 8*8= 64
9*1= 9 9*2= 18 9*3= 27 9*4= 36 9*5= 45 9*6= 54 9*7= 63 9*8= 72 9*9= 81
示例2:打印三角形
使用双层循环
外层循环控制输出行数i:1-10
内层循环:
- 第一个循环打印每行空格j
- 第二个循环打印数字k
- i(取值) 1 2 3 … 9
- j(个数) 9 8 7 1 j<=10-i
- k(个数) 1 2 3 9 k<=i
解答:
[root@localhost test5]# vim shili.sh
#!/bin/bash
for ((i=1; i<10; i++))
do
for ((j=1; j<=10-i; j++))
do
echo -n " ";
done
for ((k=1; k<=i; k++))
do
echo -n "$i "
done
echo ""
done
[root@localhost test5]# bash shili.sh
示例3:打印杨辉三角
分析:杨辉三角
1、每行数字左右对称,由1开始逐渐变大,然后变小,回到1。
2、第n行的数字个数为n个。
3、第n行数字和为2^(n-1)。(2的(n-1)次方)
4、每个数字等于上一行的左右两个数字之和。
5、将第2n+1行第1个数,跟第2n+2行第3个数、第2n+3行第5个数……连成一线,这些数的和是第2n个斐波那契数。将第2n行第2个数,跟第2n+1行第4个数、第2n+2行第6个数……这些数之和是第2n-1个斐波那契数。
6、第n行的第1个数为1,第二个数为1×(n-1),第三个数为1×(n-1)×(n-2)/2,第四个数为1×(n-1)×(n-2)/2×(n-3)/3…依此类推。
7.两个未知数和的n次方运算后的各项系数依次为杨辉三角的第(n+1)行
解答:
[root@localhost test5]# vim yanghui.sh
#!/bin/bash
if (test -z $1)
then
read -p "Input Max Lines:" MAX
else
MAX=$1
fi
i=1
while [ $i -le $MAX ] #i行控制
do
j=1
while [ $j -le $i ] #j列控制
do
f=$[i-1] #f=i-1 这是另一种计算写法。
g=$[j-1] #g=j-1 这是另一种计算写法。
if [ $j -eq $i ] || [ $j -eq 1 ] ; then
declare SUM_${i}_$j=1 #声明变量 头尾都是1
else
declare A=$[SUM_${f}_$j] #取上一行的j列变量
declare B=$[SUM_${f}_$g] #取上一行的j-1列变量
declare SUM_${i}_$j=`expr $A + $B` #声明并计算当前变量的值
fi
echo -en $[SUM_${i}_$j]" " #输出当前变量
let j++
done
echo #换行
let i++
done
[root@localhost test5]# bash yanghui.sh
Input Max Lines:4
1
1 1
1 2 1
1 3 3 1
5.4 利用break和continue语句控制循环
在Shell中的循环结构中,还有2个语句非常有用,即break和continue语句。前者用于立即从循环中退出;而后者则用来跳过循环体中的某些语句,继续执行下一次循环。
break语句的作用是立即跳出某个循环结构。break语句可以用在for、while或者until等循环语句的循环体中。
continue语句则比较有趣,它的作用不是退出循环体。而是跳过当前循环体中该语句后面的语句,重新从循环语句开始的位置执行。
示例:
[root@localhost test5]# vim for_break.sh
#!/bin/bash
for i in `seq 10`
do
if [ $i -eq 4 ]
then
break
fi
echo $i
done
[root@localhost test5]# bash for_break.sh
1
2
3
[root@localhost test5]# vim for_continue.sh
#!/bin/bash
for i in `seq 10`
do
if [ $i -eq 4 ]
then
continue
fi
echo $i
done
[root@localhost test5]# bash for_continue.sh
1
2
3
5
6
7
8
9
10
练习:
-
使用case实现成绩优良差的判断。
-
for创建20个用户,用户前缀由用户输入,用户初始密码由用户输入。
例如:test01,test10。 -
用for通过ping测试指定网段的主机,网段由用户输入,例如用户输入192.168.2 ,则ping 192.168.2.10 — 192.168.2.20
将能够访问的主机的主机名写入/tmp/host_up.txt,将无法访问的主机主机名写入/tmp/host_down.txt。
- 使用for实现批量主机root密码的修改,成功或失败都必须记录。
提示:主机IP存放在一个文件中
SSH:实现公钥认证,执行远程中主机命令 实现公钥认证
#ssh-keygen 在用于管理的主上生成密钥对
#ssh-copy-id -i 192.168.2.3