在实际工作中,会遇到某项任务重复执行,或者需要重复执行的命令中,只有个别参数不同。比如,测试主机连通性的ping命令,创建批量用户等操作。

这些任务的共同点就是简单且重复,循环语句就可以帮助解决工作中这种难题,提高工作效率,节省大量代码,同时也会相应想节省内存。

本文提到的有 5 中循环,分别是 for、while、until、case、select

for 循环语句

语句结构

for 变量名 in 取值列表
do
  命令序列
done

变量名:自定义一个不存在的变量名

取值列表:为变量名赋值,多个取值列表之间用空格隔开

命令序列:位于do…done之间,用来执行重复性任务,也叫循环体

for语句的执行流程,变量名从取值列表中,获取第一个变量值,并执行do…done之间的命令序列,然后获取取值列表中第二个变量值,执行命令序列,直到取值列表中的值全部获取结束。最后done结束循环。

for_cir

如:检测主机连通性

#!/bin/bash
HOST="192.168.1."
for PING in `seq 254`
do
  ping -c 3 -i 0.2 -w 3 ${HOST}${PING}
done
# seq 254可以将取值列表定义为1-254,根据实际情况自己改变即可。

for语句结构(C格式)

for (( 变量;条件;自增/自减运算 ))
do
  命令序列
done

如:检测ip为例

PING变量的起始值为1,每循环一次加1,直到大于等于10结束

#!/bin/bash
HOST="192.168.1."
for (( PING=1;PING<10;PING++ ))
do
  ping -c 3 -i 0.2 -w 3 ${HOST}${PING}
done

C格式的for循环也可以指定多个变量

for (( 变量1,变量2;条件;自增/自减运算 ))
do
  命令序列
done

如:

for (( a=0,b=9;a<10,b>0;a++,b-- ))
do
  echo $a $b
done
# 输出为
0 9
1 8
2 7
3 6
4 5
5 4
6 3
7 2
8 1

无限循环

应该有很多人知道,while true可以做到无限循环,但不知道 for 也可以做到,知道就可以,不建议使用,格式如下

for ((;;))
do
  命令序列
done

循环控制

以下循环控制的命令,while 循环同样适用

sleep

控制循环的节奏:当执行一个无限循环会消耗服务器硬件资源的脚本时,可以通过控制循环的节奏,缓解资源消耗

只需要在循环中加入sleep的命令即可

#!/bin/bash
IP="172.16.182.63"

UP_LOG="/var/log/ping_up.log"
DOWN_LOG="/var/log/ping_down.log"

for ((;;));do
  DATE=`date "+%F %H:%M:%S"`
  ping -c1 $IP > /dev/null
  if [ $? -eq 0 ];then
    echo -e "$DATE HOST $IP is \033[32mUp\033[0m" >> $UP_LOG
  else
    echo -e "$DATE HOST $IP is \033[31mDown\033[0m" >> $DOWN_LOG
  fi
  sleep 60
done

每隔60秒检测一遍主机是否存活

continue

跳过某次循环

跳过某次循环,继续执行下一次循环,表示循环体内 continue 下面的代码不执行。

很多人可能会对跳过循环有误解,我刚学习的时候,不知道是老师没讲明白,还是我没听明白,反正感觉 continuebreak 是一个功能。在整个循环的过程中,遇到某个特殊的变量值,不想让它使用该值执行命令,则使用continue

如:还是以ping主机为例

# 脚本
NET="172.16.182."
IP=`seq 10`
for i in $IP;do
  if [ $i -eq 4 ];then
    continue
  fi
  IP=${NET}$i
  DATE=`date "+%F %H:%M:%S"`
  ping -c1 $IP > /dev/null
  if [ $? -eq 0 ];then
    echo -e "$DATE HOST:$IP is \033[32mUp\033[0m"
  else
    echo -e "$DATE HOST:$IP is \033[31mDown\033[0m"
  fi
done

# 执行结果
# 可以看到结果中是没有172.16.182.4的,是因为在循环到值为4时被跳过了
2021-04-15 10:27:32 HOST:172.16.182.1 is Down
2021-04-15 10:27:35 HOST:172.16.182.2 is Down
2021-04-15 10:27:38 HOST:172.16.182.3 is Down
2021-04-15 10:27:41 HOST:172.16.182.5 is Down
2021-04-15 10:27:44 HOST:172.16.182.6 is Down
2021-04-15 10:27:47 HOST:172.16.182.7 is Down
2021-04-15 10:27:50 HOST:172.16.182.8 is Down
2021-04-15 10:27:53 HOST:172.16.182.9 is Up
2021-04-15 10:27:53 HOST:172.16.182.10 is Up

break

跳出循环

也就是终止循环,满足判断条件后,break打断,不再继续后面的所有循环

还以上面的代码为例

# 脚本,将以上代码中的continue改为break
NET="172.16.182."
IP=`seq 10`
for i in $IP;do
  if [ $i -eq 4 ];then
    break
  fi
  IP=${NET}$i
  DATE=`date "+%F %H:%M:%S"`
  ping -c1 $IP > /dev/null
  if [ $? -eq 0 ];then
    echo -e "$DATE HOST:$IP is \033[32mUp\033[0m"
  else
    echo -e "$DATE HOST:$IP is \033[31mDown\033[0m"
  fi
done

# 结果,可以看到满足break条件后,循环结束
2021-04-15 10:44:06 HOST:172.16.182.1 is Down
2021-04-15 10:44:09 HOST:172.16.182.2 is Down
2021-04-15 10:44:12 HOST:172.16.182.3 is Down

while 循环语句

for 循环的循环条件是手动申明的,而 while 循环,循环条件是根据条件判断来决定的,for 一般不会出现死循环,而 while 循环就比较容易出现死循环的情况。

for 循环用来操作已知次数的变量体,while 循环的循环次数不受人为控制,只有根据条件判断来决定是否结束循环的场景

语句结构

while [ 条件表达式 ]
  do
      命令序列
  done

while [ 1 -gt 2 ]  或者 (( 1 > 2 ))
  do
    命令序列
  done

条件表达式:用来决定循环次数

命令序列:位于do…done之间,用来执行重复性任务,也叫循环体

示例
打印输出1-5

num=1
while [ $num -le 5 ]; do
  echo $num
  let num++
done

比较运算

字符串比较

read -p "输入quit则退出循环:" choose
while [ $choose != "quit" ]; do
  echo "你输入的是:$choose"
  read -p "输入quit则退出循环:" choose
done

逻辑运算

当有多个条件时,使用逻辑运算

a=1
b=2
c=3
d=4
e=5
# 必须满足所有条件
while (( $a < $b )) && [ $c -gt $d ] && [ $e -ge $e ]; do
  echo "yes"
done
# 只满足其中一个条件
while (( $a < $b )) || [ $c -gt $d ] || [ $e -ge $e ]; do
  echo "yes"
done

文件类型判断

和 if 语句的判断方式时一样的

# 该路径不是目录时条件成立
while [ ! -d /tmp/xxx ]; do
  echo "非目录"
  sleep 1
done

特殊条件

  • 冒号(:):适用于死循环,条件永远成立
  • true:和冒号作用一样
  • false:条件永远不成立,也表示永远不循环
while true; do
  echo "yes"
done

不要轻易尝试死循环,尤其是在脚本中,除非测试过程中,在死循环中加入判断来退出循环。
while 循环与 for 循环一样可以使用循环控制语句(continue/break/sleep)

实战示例

这两天我就正好使用 while 写了个循环,主要用来保存生产中用到更新版本的包的数量,因为不可能把所有构建的包都保存,但是为了版本回滚,也不可能都删除,否则重新构建也是耗时的。每次构建结束后,将包存储在一个地方,并根据想要保留的版本数量和实际包的数量作比较,条件成立则删除多余的包,反之就会退出循环

keep_version=2   # 想要保留的版本
num=$(ls /data/backup | wc -l)
# 将现有包的数量与想要保留包的数量作比较
while (( "$num" > "$keep_version" )); do
    # ls -rt可以将文件根据时间排序,第一个为时间最远的文件
    OldFile=$(ls -rt /data/backup | grep "*.war" | head -1)
    echo "date 删除多余旧版本: $OldFile"
    # 删除多出来的旧版本
    rm "$OldFile"
    (( num-- ))
    # 直到*.war的包只剩2个,则循环停止,脚本退出
done

until 循环语句

一个可能失传的循环语句,甚至有的人学都不会学到,与 while 循环语句结构一样,但是条件与之相反,条件不成立时才记执行循环体

语句结构

until [ 条件表达式 ]
  do
      命令序列
  done

until [ 1 -gt 2 ]  或者 (( 1 > 2 ))
  do
    命令序列
  done

示例

当 num 变量值为20时,退出循环,until 条件为 num 小于10,意思就是 num 大于10的时候是不成立的,则会开始循环,如果 num 起始值就小于 10,将不会开始循环。

num=11
until [ $num -lt 10 ]; do
    echo $num
    let num++
    if [ $num -gt 20 ];then
        break
    fi
done

说实话,一般没什么人用这个循环,了解就行

case 循环语句

在运维的学习过程中,见到过使用 命令+参数 的方式启动服务,比如:mysqld start,其实很多关于服务的启动脚本都是用到了 case 循环,一般用于选择性执行对应参数的命令。

语句结构

case 变量 in
  参数1)
    命令
    ;;
  参数2)
    命令
    ;;
  *)
    # 不符合以上条件的都将执行此处的命令
    命令
    ;;
esac

每个参数必须以右括号结束,命令结尾以双分号结束,case 中的 * 和 if 中的 else 作用类似。

举一个上面说到的示例

#!/bin/bash
case $1 in
  start)
    nginx && echo "nginx服务启动"
    ;;
  stop)
    nginx -s quit && echo "nginx服务停止"
    ;;
  restart)
    nginx -s reload && echo "nginx服务重启成功"
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    ;;
esac

以上就是一个nginx启动的简单脚本,根据需求活学活用即可。

参数中支持的正则有:*、?、[ ]、[.-.]、|

#!/bin/bash
case $1 in
  [0-9])  # 表示0-9任意单个数字
    echo "数字"
    ;;
  [a-zA-Z]?)   # [a-zA-Z]表示任意单个大小写字母,?表示任意单个字符
    echo "字母+任意单个字符"
    ;;
  feiyi|mupei)  # 表示feiyi或者mupei
    echo Blog
    ;;
  *)   # 不符合以上,则执行这里
    echo "Usage: $0 {start|stop|restart}"
esac

我写过一个脚本是用来发布版本的,可以参考

set -eu
deploy_module=$1

echo "正在发布: ${deploy_module}"
read -p "请复制 jenkins 构建后输出 ${deploy_module} 的文件URL到此处: " -r pack_name

case "${deploy_module}" in
    "nuxt-app")
        cd /data/"${deploy_module}"
        wget "$pack_name"
        pack_name=$(ls -- *.tar.gz)
        tar zxf "$pack_name" -C nuxt-app && rm -f "$pack_name"
        echo "前端静态页面更新完成"
    ;;
    "exchange-vue-server-home")
        cd /data/"${deploy_module}"/"${deploy_module}"
        wget "$pack_name"
        pack_name=$(ls -- *.tar.gz)
        tar zxf "$pack_name" -C app && rm -f "$pack_name"
        npm run stop-"${deploy_module}" && sleep 5 && \
        npm run start-"${deploy_module}" && echo "${deploy_module} 发版成功"   
    ;;
    "exchange-vue-server-ex"|"exchange-h5-server-ex")
        cd /data/"${deploy_module}"/"${deploy_module}"
        wget "$pack_name"
        pack_name=$(ls -- *.tar.gz)
        tar zxf "$pack_name" -C app/dist && rm -f "$pack_name"
        npm run stop-"${deploy_module}" && sleep 5 && \
        npm run start-"${deploy_module}" && echo "${deploy_module} 发版成功"
    ;;
    *)
        echo "使用方法:"
        echo -e "\t sh $0 nuxt-app"
        echo -e "\t sh $0 exchange-vue-server-ex"
        echo -e "\t sh $0 exchange-h5-server-ex"
        echo -e "\t sh $0 exchange-vue-server-home"
    ;;
esac

select 循环语句

select 是一个格式类似于 for 循环的语句,使用起来类似 case 循环的语句,它本身还是个无限循环,但又和 while 不一样,只会每次让你选择,直至 ctrl + c 打断,或者循环中加入判断执行 exit 或者 break 等循环控制命令。

语句结构

select 变量名 in 取值列表
do
    命令序列
done

示例

select num in 1 2 3 4 5 quit
do
    if [ $num = "quit" ];then
      exit 0
    fi
    echo $num
done
# 输出如下
1) 1
2) 2
3) 3
4) 4
5) 5
6) quit
#? 

#? 是个提示符,在这里选择对应的序号即可。如果没有判断来做退出循环,则会一直让你进行选择。

将取值列表也可以换为数组,与上方示例效果一致

NUM=(1 2 3 4 5 quit)
select num in ${NUM[@]}
do
    if [ $num = "quit" ];then
      exit 0
    fi
    echo $num
done

也可以通过定义 PS3 的值来改变提示符 #?

NUM=(1 2 3 4 5 quit)
PS3="请输入你想要的选项前的序号:"
select num in ${NUM[@]}
do
    if [ $num = "quit" ];then
      exit 0
    fi
    echo $num
done
# 输出如下
1) 1
2) 2
3) 3
4) 4
5) 5
6) quit
请输入你想要的选项前的序号:

搭配 while 的无限循环和 break 可以做到,每次显示选项菜单,原理就是每次都通过 break 结束 select 循环,再通过 while 循环开始再一次的 select 循环。

NUM=(1 2 3 4 5 quit)
PS3="请输入你想要的选项前的序号:"
while true;do
  select num in ${NUM[@]}
  do
    if [ $num = "quit" ];then
      exit 0
    fi
    echo $num
    break
  done
done

总结

各种循环的套用,可以自己尝试结合使用,没有正确的,只有合适的,能达到目的,怎么样都对

评论




正在载入...
PoweredHexo
HostedAliyun
DNSAliyun
ThemeVolantis
UV
PV
BY-NC-SA 4.0