Shell 编程之循环语句


引言

在实际工作中,经常会遇到某项目需要多次执行得情况,而每次执行时仅仅是处理的对象不一样,命令都是相同的。当面对各种列表重复任务时,使用简单的 if 语句很难满足要求,并且顺序编写全部代码更是显得异常繁琐,这时候就可以使用 for 循环语句,可以很好的解决这样的问题。


一、使用 for 循环语句

1. for 语句的结构

  • 使用 for 循环语句时,需要指定一个变量及可能的取值列表,针对每一个不同的取值重复执行相同的命令序列,直到变量值用完退出循环。
  • for 循环语句的语法结构如下
for 变量名 in 取值列表
do
	命令序列
done
  • 上述语句结构中,操作对象为用户指定名称的变量,并通过 in 关键字为该变量预先设置了一个取值列表,多个取值之间以空格进行分隔。位于 do 和 done 之间的命令序列称为“循环体”,其中的执行语句需要引用变量以完成相应的任务。

2. for 语句的执行流程

  • 首先将列表中的第1个取值赋给变量,并执行 do···done 循环体中的命令序列
  • 然后将列表中的第2个取值赋给变量,并执行循环体中的命令序列……
  • 以此类推,直到列表中的所有取值用完,最后将跳至 done 语句,表示结束循环

3. for 语句应用示例

  • 根据姓名列表批量添加用户,操作如下
[root@localhost /home]#vim /opt/user.txt      #用户列表文件

wangwu
zhangsan
lisi


[root@localhost /home]#vim fo2.sh            #批量添加或删除用户脚本

#!/bin/bash
ULIST=$(cat /opt/user.txt)
for UNAME in $ULIST
do
        useradd $UNAME                                       
#        userdel -r $UNAME &>/dev/null
        echo "123456" |passwd --stdin $UNAME &>/dev/null
        echo "创建用户成功"
#       echo "删除用户中...";sleep 2
#       echo "删除成功" 
done
wq保存并退出

#如果想要批量删除用户就可以把上面注释去掉,并把创建用户的命令给注释掉

[root@localhost /home]#. fo2.sh            #测试并确认执行的结果
创建用户成功
创建用户成功
创建用户成功
[root@localhost /home]#tail -3 /etc/passwd
wangwu:x:1001:1001::/home/wangwu:/bin/bash
zhangsan:x:1002:1002::/home/zhangsan:/bin/bash
lisi:x:1003:1003::/home/lisi:/bin/bash
  • 根据 IP 地址列表检查主机状态
[root@localhost /home]#vim /opt/ipadds.txt            #IP地址列表文件

192.168.8.1
192.168.8.2
192.168.8.3


[root@localhost /home]#vim fo3.sh                     #循环检查各主机的脚本

#!/bin/bash
HLIST=$(cat /opt/ipadds.txt)
for IP in $HLIST
do
ping -c 3 -i 0.2 -W 3 $IP &>/dev/null                 #-c是发送ping包的数量为3
        if [ $? -eq 0 ]                               #-i是收发信息的间隔时间为0.2s
then                                                  #-W是超时时间为3s
        echo "HOST $IP is up"
else
        echo "HOST $IP is down"
fi
done

[root@localhost /home]#sh fo3.sh                     #使用sh命令就不用赋予权限才能运行
HOST 192.168.8.1 is up
HOST 192.168.8.2 is up
HOST 192.168.8.3 is down

  • 密码输入正确和错误提示
[root@localhost /home]#vim fo4.sh 

#!/bin/bash
init=123456
for i in {1..3}
do
        read -p "请输入用户密码:" passwd
        if [ $passwd == $init ]
then
        echo "密码正确"
        exit
fi
done
         echo "密码错误"

[root@localhost /home]#sh fo4.sh        
请输入用户密码:1
请输入用户密码:1
请输入用户密码:1
密码错误                                  #密码输入错误三次直接退出交互界面
[root@localhost /home]#sh fo4.sh 
请输入用户密码:123456
密码正确                                  #密码输入成功提示密码输入正确

二、while 循环语句

for 循环语句非常适合于列表对象无规律,且列表来源已固定(如某个列表文件)的场合。而对于要求控制循环次数、操作对象按数字顺序编号、按特定条件执行重复操作等情况,则更适合使用 while 语句。

1. while 语句的结构

  • 使用 while 循环语句时,可以根据特定的条件反复执行一个命令序列,直到该条件不再满足时为止。但是在脚本应用中,应该避免出现死循环的情况,否则后边的命令操作将无法执行。

  • 循环体内的命令序列中应包括修改测试条件的语句,以便在适当的时候使测试条件不再成立,从而结束循环。

  • while循环语句的语法结构如下所示:

while 条件测试操作
do
	命令序列
done

2. while 语句的执行流程

  • 首先判断 while 后的条件测试操作结果,如果条件成立,则执行 do···done 循环体中的命令序列
  • 返回 while 后再次判断条件测试结果,如果条件仍然成立,则继续执行循环体
  • 再次返回到 while 后,判断条件测试结果…如此循环
  • 直到 while 后的条件测试结果不再成立为止,最后跳转到 done 语句,表示结束循环

在这里插入图片描述

3. while 语句应用示例

  • ① 批量添加规律编号的用户
[root@localhost /home]#vim useradd.sh                   #批量添加用户脚本

#!/bin/bash
PREFIX="gl"
i=1
while [ $i -le 20 ]
do
  useradd ${PREFIX}$i
  echo "123456"|passwd --stdin ${PREFIX}$i &>/dev/null
  let i++
done

[root@localhost /home]#sh useradd.sh 
[root@localhost /home]#grep "gl" /etc/passwd |tail -3
gl18:x:1021:1021::/home/gl18:/bin/bash
gl19:x:1022:1022::/home/gl19:/bin/bash
gl20:x:1023:1023::/home/gl20:/bin/bash

上述脚本代码中,使用变量 i 来控制用户名称的编号,初始赋值为1,并且当取值大于20时终止循环。在循环中,通过语句 “let i++”(等同于i=‘expr $i+1’)来使变量i的值增加1,因此当执行第一次循环后i的值将变成2,执行第2次循环后i的值将变成3…以此类推。

  • 如果想要删除添加的用户,只需要把 while循环中添加用户的命令序列改为删除用户的操作即可,操作如下:
[root@localhost /home]#vim useradd.sh

#!/bin/bash
PREFIX="gl"
i=1
while [ $i -le 20 ]
do
  userdel -r ${PREFIX}$i
#  echo "123456"|passwd --stdin ${PREFIX}$i &>/dev/null
  let i++
done

[root@localhost /home]#sh useradd.sh 
[root@localhost /home]#id gl20                  #确认用户是否删除成功
id: gl20: no such user
  • ② 猜数字大小
[root@localhost /home]#vim w4.sh 

#!/bin/bash
NUM=8
while true 
do
read -p "请输入数字:" sz
        if [ $sz -eq $NUM ];then
                echo "你猜对了!"
                break
elif [ $sz -gt $NUM ];then
                echo "你猜大了"
elif [ $sz -lt $NUM ];then
                echo "你猜小了"
        fi
done

[root@localhost /home]#sh w4.sh 
请输入数字:2
你猜小了
请输入数字:4   
你猜小了
请输入数字:6
你猜小了
请输入数字:8
你猜对了!
  • ③ 猜价格游戏
[root@localhost /home]#vim w5.sh 

#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
a=0
echo "商品实际价格范围为0~999,猜猜价格多少?"
while true
do
read -p "请输入你猜测的价格:" INT
let a++
        if [ $INT -eq $PRICE ];then
        echo "恭喜你猜对了,实际价格是$PRICE"
        echo "你总共猜了 $a次"
        break
elif [ $INT -gt $PRICE ];then
        echo "你猜高了"
   else
        echo "你猜低了"
   fi
done


[root@localhost /home]#sh w5.sh 
商品实际价格范围为0~999,猜猜价格多少?
请输入你猜测的价格:188
你猜低了
请输入你猜测的价格:288
你猜低了
...... 省略
你猜高了
请输入你猜测的价格:920
恭喜你猜对了,实际价格是920
你总共猜了 12

三、Shell 函数

1. 概述

  • 在编写shelI脚本时,你经常会发现在多个地方使用了同一段代码。如果只是一小段代码,一般也无关紧要。但是在shell脚本中多次重写大块代码段就太累人了。
  • 为了解决这个问题,我们可以将 shell 脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次重复使用它了。

2. 函数的基本格式

[function] 函数名(){
  命令序列
[return x]                           #return x 的作用是命令序列执行完后返回给系统一个值,可省略  
}

或者
                                     #也可以省略掉[function],它表示该函数的功能

函数名() {                            #函数名后面()是没有内容的
  命令序列                            #我们执行的命令内容放在{}里面
}
  • 函数一结束就取返回值,退出的状态码必须是0~255
  • 由于退出状态码必须小于256,函数的结果必须生成一个小于256的整数值,任何大于256的值都会产生一个错误值。

3. 函数应用示例

  • 定义一个输入两个数求和的脚本
[root@localhost /home]#vim summ.sh

#!/bin/bash
sum(){
read -p "请输入第一个数:" NUM1
read -p "请输入第二个数:" NUM2
echo "你输入的二个数为:$NUM1 和 $NUM2."
SUM=$[$NUM1 + $NUM2]
echo "两个数和为:$SUM"

}
sum

[root@localhost /home]#sh summ.sh 
请输入第一个数:8
请输入第二个数:9
你输入的二个数为:89.
两个数和为:17
  • 返回值的范围
[root@localhost /home]#vim retun.sh 
#!/bin/bash
function test1 {
read -p "请输入数字:" num
return $[num*2]

}
test1
echo $?

[root@localhost /home]#sh retun.sh 
请输入数字:3
6
[root@localhost /home]#sh retun.sh 
请输入数字:127
254
[root@localhost /home]#sh retun.sh 
请输入数字:128
0
  • 函数的阶乘
[root@localhost /home]#vim cc.sh

#!/bin/bash
fa () {
if [ $1 -eq 1 ]
then
echo 1
else
        local tp=$[ $1 - 1 ]
        local res=$(fa $tp)
        echo $[ $1 * $res ]
fi
}
read -p "请输入:" num
res=$(fa $num)
echo $res

[root@localhost /home]#sh cc.sh      #5*4*3*2*1=120
请输入:5
120
[root@localhost /home]#sh cc.sh      #3*2*1=6
请输入:3
6

四、数组

1. 定义数组

  • 可以在单行中使用数值列表来定义一个数组
array_var=(test1 test2 test3 test4)
#这些值将会存储在以0为起始索引的连续位置上
或者
#将数组定义为一组“索引—值”
array_var=[0]="test1"
array_var=[1]="test2"
array_var=[2]="test3"
array_var=[3]="test4"
array_var=[4]="test5"
array_var=[5]="test6"
  • 精确的给每一个下标索引定义一个值加入数组,索引数字可以不连续
num=([0]=55 [1]=66 [2]=77 [4]=88)
数组名=([0]=value [1]=value [2]=value. . .)
  • 先把要加入数组的元素全部先赋值给一个变量,然后引用这个变量加入到数组
list="11 12 13 14"
num=($list)

2. 获取数组长度

  • 打印出特定索引的数组元素内容
#echo ${array_var [0]}
test1
index=5
#echo ${array_var[$index]}
test6
  • 以列表形式打印出数组中的所有值
#echo ${array_var[*]}
test1 test2 test3 test4 test5 test6
或者
#echo ${array_var[@]}
test1 test2 test3 test4 test5 test6
  • 打印数组长度(即数组中元素的个数)
#echo ${#array_var[*]}6
  • 数组元素的遍历
[root@localhost ~]#arr=(1 2 3 4 5 6)
[root@localhost ~]#for i in ${arr[*]}
> do
> echo $i
> done
1
2
3
4
5
6

3. 元素切片

[root@localhost ~]#arr=(1 2 3 4 5 6 7 8 9)
[root@localhost ~]#echo ${arr[*]}
1 2 3 4 5 6 7 8 9
[root@localhost ~]#echo ${arr[*]:2:3}         #提取出第二个数后面的三个值
3 4 5
[root@localhost ~]#echo ${arr[*]:2:5}
3 4 5 6 7
[root@localhost ~]#echo ${arr[*]:2:2}
3 4
[root@localhost ~]#echo ${arr[*]:0:6}
1 2 3 4 5 6

4. 元素替换

[root@localhost ~]#arr=(1 2 3 4 5 6 7 8 9)
[root@localhost ~]#echo ${arr[*]}
1 2 3 4 5 6 7 8 9
[root@localhost ~]#echo ${arr[*]/3/88}      #3指的是查找数主里面的字符,88指的是需要替换的字符
1 2 88 4 5 6 7 8 9
[root@localhost ~]#echo ${arr[*]/5/66}
1 2 3 4 66 6 7 8 9

5. 元素删除

[root@localhost ~]#arrt=(1 2 33 4 566 6)
[root@localhost ~]#echo ${arrt[*]}
1 2 33 4 566 6
[root@localhost ~]#unset arrt[2]         #删除的时候是以下标的形式来进行删除,下标2对应的字符为33    
[root@localhost ~]#echo ${arrt[*]}
1 2 4 566 6
[root@localhost ~]#unset arrt[4]         #下标4对应的字符为566
[root@localhost ~]#echo ${arrt[*]}
1 2 4 6

6. 冒泡排序

  • 概念:
    指的是它会遍历若干次要排序的数列,每次遍历时,它都会从前往后依次的比较相邻两个数的大小;如果前者比后者大,则交换它们的位置。这样,一次遍历之后,最大的元素就在数列的末尾! 采用相同的方法再次遍历时,第二大的元素就被排列在最大元素之前。重复此操作,直到整个数列都有序为止。

  • 案例

[root@localhost /home]#vim mp.sh 

#!/bin/bash
array=(20 40 30 10 60 50)
echo "old_array:${array[*]}"
lt=${#array[*]}
#定义比较轮数,比较轮数为数组长度减1,从1开始;
for ((i=1;i<$lt;i++))
do
#确定比较元素的位置,比较相邻的两个元素,较大的数往后放,小的数往前放,并且每
轮比较次数需要随着轮数递减
        for ((j=0;j<$lt-i;j++))
        do
           #定义第一个元素的值
           first=${array[$j]}
           #定义第二个元素的值
           k=$[$j+1]
           second=${array[$k]}
           #如果第一个元素比第二个元素大,就进行互换
           if [ $first -gt $second ]
           then
           #把第一个元素值存放到临时变量中
                tmp=$first
           #把第二个元素值赋给第一个元素
                array[$j]=$second
           #把临时变量(第一个元素原值)赋给第二个元素
                array[$k]=$tmp
         fi
        done

done
echo "new_array:${array[*]}"

下面以数列{20,40,30,10,60,50}为例,演示它的冒泡排序过程:

mark

  • 流程如下:

① 我们先分析第1趟排序
当i=5,j=0时,a[0]<a[1];此时,不做任何处理。
当i=5,j=1时,a[1]>a[2];此时,交换a[1]和a[2]的值;交换之后,a[1]=30,a[2]=40。
当i=5,j=2时,a[2]>a[3];此时,交换a[2]和a[3]的值;交换之后,a[2]=10,a[3]=40。
当i=5,j=3时,a[3]<a[4];此时,不做任何处理!
当i=5,j=4时,a[4]>a[5];此时,交换a[4]和a[5]的值;交换之后,a[4]=50,a[3]=60。

② 第1趟排序完之后,数列 {20,40,30,10,60,50} 变成了 {20,30,10,40,50,60}。此时,数列末尾的值最大。

③ 根据这种方法可以知道:
第2趟排序完之后,数列中 a[5…6] 是有序的。
第3趟排序完之后,数列中 a[4…6] 是有序的。
第4趟排序完之后,数列中 a[3…6] 是有序的。
第5趟排序完之后,数列中 a[1…6] 是有序的。
第5趟排序之后,整个数列也就是有序的了。

  • 上面排序的输出结果如下
[root@localhost /home]#sh mp.sh 
old_array:20 40 30 10 60 50
new_array:10 20 30 40 50 60
  • 方法2,操作如下
[root@localhost /home]#vim mp1.sh

#!/bin/bash
NUM=(90 70 60 40 50 30)
echo "old_NUM:${NUM[*]}"
for ((i=0;i<${#NUM[*]};i++))
   do
        for ((j=i+1;j<${#NUM[*]};j++))
         do
           if [ ${NUM[$i]} -gt ${NUM[$j]} ]
            then
              temp=${NUM[$i]}
                NUM[$i]=${NUM[$j]}
                NUM[$j]=$temp
                fi
                done
                done
echo "new_NUM:${NUM[*]}"


[root@localhost /home]#sh mp1.sh 
old_NUM:90 70 60 40 50 30
new_NUM:30 40 50 60 70 90

五、Shell 脚本调试

  • 在Shell 脚本开发中,经常碰到一些规范方面的问题,例如忘了便用引号或在 if 语句末尾处忘记加 fi 结束。

  • 要注意把复杂的脚本简单化,要思路清晰,并且分段实现。当执行脚本时出现脚本错误后,不要只看那些提示的错误行,而是要观察整个相关的代码段。

  • 为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。

1. echo 命令

echo命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入 echo 命令,采用的是分段排查的方式。

2. bash 命令

  • 除了echo 命令之外,bash Shell 也有相应参数可以调试脚本。

  • 使用 bash 命令参数调试,命令的语法如下:

sh [-nvx] 脚本名
  • 常用的参数如下所示

-n:不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任何内容,如果有问题会提示报错。

-V:在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出错误提示。

-x:将执行的脚本内容输出到屏幕上,这是个对调试很有用的参数。

3. set 命令

  • 当脚本较长时,可以使用 set 命令指定调试一段脚本

  • 常用参数如下:

set -x 					#开启调试模式
set +x                  #关闭调试模式
  • 示例
[root@localhost /home]#cat 1.sh 
#!/bin/bash
set -x
for ((i=1;i<=5;i++))
do
	echo $i
done

#开启调试模式以后会看到每段的反馈结果
[root@localhost /home]#sh 1.sh 
+ (( i=1 ))
+ (( i<=5 ))
+ echo 1
1
+ (( i++ ))
+ (( i<=5 ))
+ echo 2
2
+ (( i++ ))
+ (( i<=5 ))
+ echo 3
3
+ (( i++ ))
+ (( i<=5 ))
+ echo 4
4
+ (( i++ ))
+ (( i<=5 ))
+ echo 5
5
+ (( i++ ))
+ (( i<=5 ))

总结

Shell 脚本是千变万化的,正所谓条条大路通罗马,shell 脚本中也是如此。一个脚本可以有无数种写法,但是思路都是相通的,只要有了思路脚本就自然会写的很顺畅!

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

头发莫的了呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值