第五章、流程控制之循环
5.1 步进循环语句for
for循环是最简单,也是最常用的循环语句。与其他的程序设计语言一样,for循环都是初学者在学习循环结构时的入门课程。for循环通常用于遍历整个对象或者数字列表。按照循环条件的不同,for循环语句可以分为带列表的for循环、不带列表的for循环以及类C风格的for循环。本节将介绍这3种不同的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会将列表中的第1个元素的值赋给变量variable,然后再次执行循环体。当list列表中的所有的元素都被访问后,for循环结构终止,程序将继续执行done语句后面的其他的语句。
示例:1:直接列出变量列表所有元素
方法1:直接列出元素方法
# cat for-1.sh
#!/bin/sh
for IP in 192.168.1.101 192.168.1.102
do
ping $IP &>/dev/null
[ $? = 0 ] && echo up || echo down
done
方法2:使用大括号
# cat for-2.sh
#!/bin/sh
for IP in 192.168.1.10{1..5}
do
echo $IP
done
-------------------------
for i in {1..100};do
let sum+=i
#sum=sum+i
done
echo "$1+2+3+4.."=$sum
方法3:使用seq https://www.cnblogs.com/faberbeta/p/linux-shell007.html
# cat for-3.sh
#!/bin/sh
for IP in $(seq -f "192.168.1.10%1g" 1 5) 192.168.1.1 192.168.1.2
do
echo $IP
done
示例2:获取当前目录下的文件名作为变量列表打印输出
# cat for-4.sh
#!/bin/sh
for FILE in $(ls -F | grep -v /$)
do
echo $FILE
done
---------------------------------
for f in `ls` ;do
if [ -d $f ];then
echo $f
fi
done
5.1.2 不带列表的for循环语句
在某些特殊情况下,for循环的条件列表可以完全省略,称为不带列表的for循环语句。如果没有为for循环提供条件列表,Shell将从命令行获取条件列表。不带列表的for循环语句的一般语法如下:
for variable
do
statement1
statement2
...
done
./f.sh `seq 10`
由于系统变量$@同样可以获取所有的参数,所以以上的语法等价于以下语法:
for variable in $@或$*
do
statement1
statement2
...
done
示例:
[root@localhost ~]# cat f1.sh
#! /bin/bash
#不带条件列表
for arg
do
#输出每个参数
echo "$arg"
done
[root@localhost ~]# sh f1.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通常用来改变条件变量的值,例如递增或者递减等。
++i是先i自加1,然后在使用i的值
i++是先用i的值,在i自加1。
[root@localhost ~]# x=1
[root@localhost ~]# y=1
[root@localhost ~]# echo $((x++))
1
[root@localhost ~]# echo $((++y))
2
示例3:批量创建用户
要求:
用户名以test开头,按数字序号变化;
一共添加30个账号,即test01,tes02…,test30
用户初始密码为123456
思路:
用户0-9:
useradd test01 test02
用户10-30
useradd test
分析:
根据要求使用循环语句完成
创建用户使用useradd命令,根据命名规则,10以下的需要加0
初始账号设置方法(echo “123456” |passwd –stdin username)
示例代码:user01 user10
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}
示例4:编写一个 Shell 程序,实现判断当前网络(假定为192.168.1.0/24,根据实际情况实现)里,当前在线用户的IP
分析:
笔试常考题目,使用循环
for ((i=1;i<=254;i++))
do
if pint -c 2 -W 1 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循环语句的基本语法如下:
while expression
do
statement1
statement2
...
done
在上面的语法中,expression表示while循环体执行时需要满足的条件。虽然可以使用任意合法的Shell命令,但是,通常情况下,expression代表一个测试表达式。与其他的循环结构一样,do和done这2个关键字之间的语句构成了循环体。
5.2.1 while循环读取文件
方法一:采用exec读取文件,然后进入while循环处理(一般不使用该方法)
exec < File
while read line
do
statement1
done
方法二:使用cat读文件,然后通过管道进入while循环处理
cat File | while read line
do
statement1
done
方法三:通过在while循环结尾,使用输入重定向方式
while read line
do
statement1
done < file
变量参数是按换行符分割
exec:
[root@localhost day05]# exec 命令 # 执行完该命令后退出
5.2.2 while语句的示例
示例1:猜商品价格 通过变量RANDOM获得随机数
提示用户猜测并记录次数,猜中后退出循环
分析:
产生随机数
猜中退出循环,显然要用死循环(while true),满足条件退出程序
通过if条件判断,根据用户输入数值和生成随机数比较
记录猜测次数,每猜测一次加1
通过系统环境变量($RANDOM)产生随机数
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
----------------------------------------------------------------------------
利用RANDOM取随机数
shell有一个环境变量RANDOM,范围是0--32767%100
如果我们想要产生0-25范围内的数:$(($RANDOM%26)) 在$(()) 是可以省略取值的$符号的。(All tokens in the expression undergo parameter expansion, string expansion, command substitu-tion, and quote removal.)
用这个环境变量对26取模即可。
如果想得到1--68范围内的数 : $(($RANDOM%68+1 ))
如果想得到6--87范围内的数 : $(($RANDOM%82+6 ))
####### 猜值游戏:1-100
[root@localhost day05]# vim d.sh
p=$(($RANDOM%100+1))
while true;do
read -p "请输入商品的价格(1-100):" price
if [ $price -eq $p ];then
echo "恭喜你猜对了,商品的价格为$price"
break
elif [ $price -lt $p ];then
echo "猜小了,再来一次哦"
continue
else
echo "猜大了,再来一次哦"
continue
fi
done
[root@localhost day05]# bash d.sh
请输入商品的价格(1-100):50
猜大了,再来一次哦
请输入商品的价格(1-100):25
猜大了,再来一次哦
请输入商品的价格(1-100):12
猜小了,再来一次哦
请输入商品的价格(1-100):20
猜大了,再来一次哦
请输入商品的价格(1-100):18
恭喜你猜对了,商品的价格为18
示例2:while读取文件
工作过程中遇到要从一个ip列表中获取ip port然后ssh ip 到目标机器进行特定的操作
# cat iplist
192.168.147.101 5225
192.168.147.102 2234
192.168.147.103 4922
# cat while-read.sh
#!/bin/sh
while read line
do
IP=$(echo $line |awk '{print $1}')
PORT=$(echo $line |awk '{print $2}')
echo "IP: $IP, PORT: $PORT"
done <iplist
示例3:将之前用for语句创建的test01-test30用户删除
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.2.3 until循环语句
until循环语句同样也存在于多种程序设计语言中。顾名思义,until语句的作用时将循环体重复执行,直到某个条件成立为止。恰当地使用until语句,可以收到事半功倍地效果。本节将详细介绍until语句的使用方法。
until循环语句的功能是不断地重复执行循环体中的语句,直至某个条件成立。until语句的基本语法如下:
until expression
do
statement1
statement2
...
done
在上面的语法中,expression是一个条件表达式。当该表达式的值不为0时,将执行do和done之间的语句;当expression的值为0时,将退出until循环结构,继续执行done语句后面的其它的语句。
示例:将之前用for语句创建的test01-test30用户删除
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.2.4 select循环语句
select循环语句的主要功能是创建菜单,在执行带有select循环语句脚本时,输出会按照数字顺序的列表显示一个菜单,并显示提示符(默认是#?),同时等待用户输入数字选择。select语句的基本语法如下:
select 变量名 [ in 菜单值列表 ]
do
statement1
statement2
...
done
示例:
#!/bin/bash
select mysql_version in 5.1 5.6
do
echo $mysql_version
done
# bash test.sh
1) 5.1
2) 5.6:
#? 1
5.1
#? 2
5.6
--------------------------------------------------
i=0
while [ $i -lt 3 ];do
select version in 1.2 2.2 3.2 ;do
echo $version
break
done
let i++
done
5.3 嵌套循环
在程序设计语言中,嵌套的循环也是一种非常常见的结构。Shell同样也支持嵌套循环。通过嵌套循环,可以完成更复杂的功能。本节将介绍Shell中嵌套循环的使用方法。
注意:校招常考题目。
示例1:打印九九乘法表
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
.....
#行 9 i=2
#列 ((j=1;j<=i;j++)) j=1 j=2
1*1
1*=2 2*2=4
.....
j*i=$((j*i))
## for循环
for i in `seq 9` #i循环次数 i=被乘数
do
for j in `seq 9` #j乘数 ++ 一行循环的次数
do
[ $j -le $i ] && echo -n "$j*$i= `echo $(($i*$j))` "
done
echo " "
done
#############################################################################
for ((i=1;i<10;i++));do
for ((j=1;j<=i;j++));do
echo -n "$j * $i = `let $i \* $j`"
done
echo ""
done
示例2:打印三角形
分析:
使用双层循环
外层循环控制输出行数i:9次 i=1 i<=9 i++ 1
内层循环:
第一个循环打印每行空格j j=1 j<=10-i j++
第二个循环打印数字k k=1 k<=i k++ 1
i(取值) 1 2 3 ... 9
j(个数) 9 8 7 1 j<=10-i
k(个数) 1 2 3 9 k<=i
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
运行后:
1
2 2
3 3 3
4 4 4 4
5 5 5 5 5
6 6 6 6 6 6
7 7 7 7 7 7 7
8 8 8 8 8 8 8 8
9 9 9 9 9 9 9 9 9
5.4 循环控制语句
在Shell中的循环结构中,还有2个语句非常有用,即break和continue语句。前者用于立即从循环中退出;
而后者则用来跳过循环体中的某些语句,继续执行下一次循环。
本节将详细介绍这2个语句的使用方法。
break语句的作用是立即跳出某个循环结构。break语句可以用在for、while或者until等循环语句的循环体中。
continue语句则比较有趣,它的作用不是退出循环体。而是跳过当前循环体中该语句后面的语句,重新从循环语句开始的位置执行。
示例:
# cat for-break.sh
#!/bin/sh
for i in `seq 10`;do
if [ $i -eq 4 ];then
break
fi
echo $i
done
# cat for-continue.sh
#!/bin/sh
for i in `seq 10`
do
if [ $i -eq 4 ]
then
continue
fi
echo $i
done
#逢3过
#1-100 n
#echo $n | grep 3 echo $? $?=0 包含了3 跳过
#m=$((n%3)) m=0跳过
#!/bin/bash
for i in {1..100};do
r=$[i%3]
echo $i| grep '3' &>/dev/null
s=`echo $?`
if [[ $r -eq 0 ]] || [[ $s -eq 0 ]];then
continue
fi
echo $i
done
练习:
1. 使用case实现成绩优良差的判断
2. for创建20用户
用户前缀由用户输入
用户初始密码由用户输入
例如:test01,test10
3. for ping测试指网段的主机
网段由用户输入,例如用户输入192.168.2 ,则ping 192.168.2.10 --- 192.168.2.20
UP: /tmp/host_up.txt
Down: /tmp/host_down.txt
4. 使用for实现批量主机root密码的修改
成功或失败都必须记录
提示:主机IP存放在一个文件中
SSH:实现公钥认证,执行远程中主机命令
实现公钥认证
# ssh-keygen 在用于管理的主上生成密钥对
# ssh-copy-id -i 192.168.2.3
编写一个shell脚本程序,使用shell编写一个菜单,分别实现列出以下内容:
[root@localhost day05]# vim cat.sh
cat << EOF
1:List you selected directory
2:Change to you selected directory
3:Create a new file
4:Edit you selected file
5:Remove you selected file
q/Q:Exit Menu
EOF
until false;do
read -p "请输入你的选择[1-5]q/Q:" c
case $c in
1)
ls;;
2)
cd;;
3)
touch file;;
4)
echo this is a > file;;
5)
read -p "请输入要删除的文件名:" f
rm -rf $f && echo "$f文件已删除"
;;
q|Q)
break;;
esac
done