在 Linux 运维和自动化脚本开发中,循环语句是实现批量处理和重复任务的核心工具。
本文将详细介绍 Shell 脚本中三种常用的循环语句:for、while 和 until,并通过丰富的实战案例,帮助你从理论走向实践,掌握高效编写 Shell 脚本的技巧。
一、for 循环:遍历列表的最佳选择
当你需要对一系列已知的对象(如用户名列表、IP 地址列表、文件名列表)执行相同操作时,for 循环是最直观、最简洁的选择。它的核心思想是 “遍历”,即依次取出列表中的每一个值,赋给变量,然后执行循环体内的命令。
1.1 for 循环的基本结构
for 循环的语法结构非常清晰:
for 变量名 in 取值列表
do
命令序列
done
变量名:你自定义的一个变量,用于在每次循环中接收 “取值列表” 中的一个值。
取值列表:由空格分隔的多个值,也可以是命令的输出结果(如 $(cat file.txt))。
do…done:这对关键字包裹的是循环体,即每次循环要执行的命令。
for 循环的执行流程是:首先将列表中的第一个值赋给变量,执行 do…done 之间的命令;然后将列表中的下一个值赋给变量,再次执行命令…… 如此类推,直到列表中的所有值都被处理完毕,最后跳到 done 语句,结束循环。
1.2 实战案例:批量操作用户与主机
案例一:根据姓名列表批量添加 / 删除用户
假设人事部门给了你一个员工姓名列表 users.txt,你需要在 Linux 服务器中为这些员工批量创建用户账号,并设置统一的初始密码。
准备姓名列表文件:
# vim /root/users.txt
chenye
dengchao
zhangjie
编写批量添加用户的脚本 uaddfor.sh:
#!/bin/bash
# 从文件中读取用户名列表
ULIST=$(cat /root/users.txt)
# 遍历列表,逐个添加用户
for UNAME in $ULIST
do
useradd $UNAME
echo "123456" | passwd –stdin $UNAME &>/dev/null
done
执行脚本并验证:
chmod +x uaddfor.sh
./uaddfor.sh
tail -3 /etc/passwd # 查看是否成功创建用户
如果需要删除这些用户,只需将脚本中的 useradd 和 passwd 命令替换为 userdel -r 即可:
#!/bin/bash
ULIST=$(cat /root/users.txt)
for UNAME in $ULIST
do
userdel -r $UNAME &>/dev/null
done
案例二:根据 IP 地址列表检查主机状态
另一个常见的场景是批量检查网络中多台主机的连通性。你可以将所有服务器的 IP 地址存入 ipadds.txt,然后用 for 循环遍历,使用 ping 命令检测每台主机是否在线。
准备 IP 地址列表文件:
# vim /root/ipadds.txt
172.16.16.1
172.16.16.22
172.16.16.220
编写主机检查脚本 chkhosts.sh:
#!/bin/bash
# 从文件中读取IP列表
HLIST=$(cat /root/ipadds.txt)
# 遍历列表,逐个ping测试
for IP in $HLIST
do
# 发送3个ping包,间隔0.2秒,超时3秒
ping -c 3 -i 0.2 -W 3 $IP &> /dev/null
# 根据上一条命令的返回值($?)判断结果
if [ $? -eq 0 ]
then
echo "Host $IP is up."
else
echo "Host $IP is down."
fi
done
执行脚本:
chmod +x chkhosts.sh
./chkhosts.sh
脚本会清晰地输出每台主机的状态,例如:
plaintext
Host 172.16.16.1 is up.
Host 172.16.16.22 is up.
Host 172.16.16.220 is down.
二、while 循环:条件驱动的循环控制
for 循环擅长处理 “列表遍历”,而 while 循环则更适合 “条件驱动” 的场景。当你需要根据某个动态变化的条件来决定是否继续循环时,while 是更好的选择。
2.1 while 循环的基本结构
while 循环的语法结构如下:
while 条件测试操作
do
命令序列
done
条件测试操作:这是 while 循环的核心。每次循环开始前,都会先判断这个条件的真假。
执行流程:如果条件为真(返回值为 0),则执行 do…done 之间的命令;执行完毕后,再次回到 while 判断条件。这个过程一直重复,直到条件测试的结果为假(返回值非 0),循环才会终止。
⚠️ 重要提示:使用 while 循环时,必须确保循环体内有语句能够改变测试条件,否则会造成 “死循环”,脚本将永远执行下去。
2.2 实战案例:批量创建规范用户与猜价格游戏
案例一:批量添加规律编号的用户
在一些培训或测试场景中,我们需要创建一批名称有规律的用户,例如 stu1、stu2、…、stu20。
编写批量创建脚本 uaddwhile.sh:
#!/bin/bash
# 定义用户名前缀
PREFIX="stu"
# 初始化计数器i
i=1
# 当i的值不大于20时,继续循环
while [ $i -le 20 ]
do
# 创建用户stu+i
useradd ${PREFIX}${i}
# 设置初始密码
echo "123456" | passwd –stdin ${PREFIX}$i &>/dev/null
# 计数器i自增1,这是避免死循环的关键
let i++
done
执行脚本并验证:
chmod +x uaddwhile.sh
./uaddwhile.sh
grep "stu" /etc/passwd | tail -3 # 查看最后三个用户
同样,删除这些用户也只需修改脚本中的命令:
#!/bin/bash
PREFIX="stu"
i=1
while [ $i -le 20 ]
do
userdel -r ${PREFIX}$i
let i++
done
案例二:猜价格游戏(完整实现)
这个案例是一个经典的交互式脚本,我们来完整实现它。脚本会先生成一个 0~999 的随机数作为 “商品价格”,然后提示用户输入猜测的数字,并给出 “太高了” 或 “太低了” 的提示,直到用户猜中为止。
#!/bin/bash
# 生成一个0-999的随机价格
PRICE=$(expr $RANDOM % 1000)
# 初始化猜测次数为0
TIMES=0
echo "商品实际价格范围为 0-999, 请猜猜看是多少?"
# 使用while true构造一个无限循环
while true
do
# 读取用户输入的猜测值
read -p "请输入你猜测的价格数目: " INT
# 猜测次数加1
let TIMES++
# 判断用户输入与实际价格的关系
if [ $INT -eq $PRICE ]
then
# 猜中了,输出恭喜信息和总次数,然后退出脚本
echo "恭喜你答对了, 实际价格是 $PRICE"
echo "你总共猜测了 $TIMES 次"
exit 0
elif [ $INT -gt $PRICE ]
then
# 猜高了,给出提示,继续循环
echo "太高了!"
else
# 猜低了,给出提示,继续循环
echo "太低了!"
fi
done
执行效果如下:
plaintext
[root@localhost ~]# ./pricegame.sh
商品实际价格范围为 0-999, 请猜猜看是多少?
请输入你猜测的价格数目: 500
太高了!
请输入你猜测的价格数目: 250
太低了!
请输入你猜测的价格数目: 375
太高了!
请输入你猜测的价格数目: 280
太高了!
请输入你猜测的价格数目: 265
太高了!
请输入你猜测的价格数目: 253
恭喜你答对了, 实际价格是 253
你总共猜测了 6 次
三、until 循环:条件为假时执行
until 循环与 while 循环在结构上非常相似,但它们的逻辑恰好相反。
while:当条件为真时,执行循环。
until:当条件为假时,执行循环;直到条件变为真时,才停止循环。
3.1 until 循环的基本结构
until 条件测试操作
do
命令序列
done
3.2 实战案例:计算累加和与在线消息推送
案例一:计算 1 到 50 的累加和
我们用 until 循环来实现从 1 累加到 50 的功能,直到 i 大于 50 时停止。
#!/bin/bash
i=0
s=0
# 当i不等于50时,继续循环
until [ $i -eq 50 ]
do
let "i=i+1" # i自增1
let "s=s+$i" # 将i累加到s中
done
echo 'sum(1..50)='$s # 输出结果
执行脚本,你会得到 sum(1..50)=1275 的正确结果。
案例二:为指定用户发送在线消息
假设公司有一台测试服务器,运维人员需要向已登录的开发人员发送紧急通知。如果用户不在线,就等待其登录后再发送。这个 “等待” 的场景非常适合用 until 或 while 来实现。
#!/bin/bash
# 检查参数数量是否足够
if [ $# -lt 2 ]; then
echo "Usage: $0 <username1> <username2> … <message>"
exit 1
fi
# 提取消息,消息是最后一个参数
message="${!#}"
# 遍历除最后一个参数外的所有用户名
for username in "${@:1:$#-1}"; do
# 检查用户是否为系统内有效用户
if ! grep -q "^${username}:" /etc/passwd; then
echo "$username is not a valid user on this system."
continue
fi
# 持续检查用户是否登录,直到其登录为止
while ! who | grep -q "^${username}"; do
echo "$username is not logged on. Waiting for the user to log in…"
sleep 60 # 等待60秒后再次检查
done
# 用户已登录,发送消息
echo "Sending message to $username…"
write "$username" <<EOF
$message
EOF
# 检查消息是否发送成功
if [ $? -ne 0 ]; then
echo "Failed to send message to $username."
fi
done
这个脚本的使用方式是 ./until-user_online_to_write.sh 用户名 消息内容,例如:
./until-user_online_to_write.sh root "Are you ready ? root"
如果 root 用户在线,消息会立即发送;如果不在线,脚本会每 60 秒检查一次,直到 root 登录后发送消息。
四、总结
Shell 脚本中的三种循环语句各有千秋,选择合适的工具能让你的脚本更加高效和优雅:
for 循环:最适合处理预定义列表的遍历,如批量操作用户、批量处理文件等,逻辑清晰,代码简洁。
while 循环:擅长处理条件为真时重复执行的场景,如根据计数器循环、交互式程序等,是实现复杂逻辑的基础。
until 循环:与 while 相反,适合处理 “直到条件满足才停止” 的场景,如等待某个事件发生。
掌握这三种循环语句,并能在实际场景中灵活运用,是编写高效、可维护的 Shell 自动化脚本的必备技能。



