shell 循环入门:让重复任务自动化起来
你有没有遇到过这样的场景?需要对 100 个文件分别执行相同的操作,比如重命名、检查权限、修改内容。如果手动一个个操作,不仅耗时,还容易出错。这时候,shell 循环就是你的救星。
想象一下,你每天早上要打开 5 个不同的终端窗口,运行 5 个不同的启动脚本。如果每次都要手动敲一遍命令,那得多累。而通过 shell 循环,你只需写一次代码,就能自动帮你完成全部操作。这就像给你的命令装上“自动播放”功能,省时又高效。
shell 循环的核心思想是:让计算机重复执行一段代码,直到满足某个条件为止。无论是处理文件、遍历列表,还是监控系统状态,shell 循环都能派上大用场。它让你从繁琐的重复劳动中解脱出来,专注于更重要的逻辑设计。
接下来,我们将从最基础的 for 循环开始,逐步深入到 while、until 以及各种实用技巧,让你真正掌握 shell 循环的精髓。
for 循环:按列表逐个执行任务
for 循环是 shell 循环中最常用的一种,它的结构非常直观:遍历一个集合中的每个元素,对每个元素执行一次指定的操作。
在 shell 中,for 循环的基本语法如下:
for 变量名 in 列表
do
命令1
命令2
# 可以添加更多命令
done
这里的“列表”可以是字符串、文件名、数字序列等。变量名用来临时存储当前正在处理的元素。
举个实际例子:假设你有一个名为 users.txt 的文件,里面每行是一个用户名,你想为每个用户创建一个目录。
for username in $(cat users.txt)
do
# 检查目录是否已存在,避免重复创建
if [ ! -d "/home/$username" ]; then
# 创建用户主目录
mkdir /home/$username
# 设置权限为 755,确保安全
chmod 755 /home/$username
echo "已为用户 $username 创建目录"
else
echo "用户 $username 的目录已存在,跳过"
fi
done
这段脚本的执行过程就像“传话游戏”:从文件中读取第一个用户名,执行创建目录的命令,然后换下一个,直到所有用户都处理完毕。
你也可以用 for 循环来生成数字序列。比如创建 10 个测试文件:
for i in {1..10}
do
# 使用 touch 命令创建文件,文件名为 test_1.txt, test_2.txt, ...
touch test_$i.txt
echo "已创建文件 test_$i.txt"
done
这个 {1..10} 语法是 Bash 的扩展,代表从 1 到 10 的连续整数。它比手动写 10 个命令高效得多。
⚠️ 注意:在 for 循环中使用
$(cat file)时,如果文件中有空格或特殊字符,可能会导致错误。推荐使用while read配合文件输入,更安全。
while 循环:当条件为真时持续执行
while 循环的逻辑是:只要条件成立,就一直执行循环体内的命令。它适用于那些不知道具体执行次数,但知道“什么时候该停”的场景。
语法结构如下:
while 条件表达式
do
命令1
命令2
done
条件表达式通常是一个测试命令,比如检查文件是否存在、变量是否等于某个值、或某个服务是否运行。
举个例子:你想监控某个日志文件,当文件大小超过 100KB 时,就发一条提醒。
log_file="/var/log/app.log"
threshold=102400 # 100KB
while [ $(du -b "$log_file" | cut -f1) -lt "$threshold" ]
do
# 每 5 秒检查一次
sleep 5
echo "当前日志大小:$(du -h "$log_file" | cut -f1)"
done
echo "警告:日志文件大小已超过 $threshold 字节!"
这个循环就像一个“守门员”:每隔 5 秒检查一次日志大小,只要还没超过 100KB,就继续等待。一旦超过,就跳出循环并发出警告。
另一个常见用途是实现用户输入验证:
choice=""
while [[ "$choice" != "yes" && "$choice" != "no" ]]
do
echo "请输入 yes 或 no:"
read choice
# 将输入转为小写,避免大小写问题
choice=$(echo "$choice" | tr '[:upper:]' '[:lower:]')
done
echo "你选择了:$choice"
这个例子展示了 while 循环在交互式脚本中的强大作用——它能确保用户输入符合预期,避免程序因无效输入崩溃。
until 循环:直到条件为真才停止
until 循环和 while 循环正好相反。它的逻辑是:只要条件为假,就继续执行;当条件为真时,循环结束。
语法如下:
until 条件表达式
do
命令1
命令2
done
它适合用于“等待某个事件发生”的场景。比如,等待某个服务启动、某个文件出现、或某个端口被占用。
举个例子:你想等待一个网络服务启动,比如 Nginx。你可以写一个脚本,持续检查服务是否已运行,直到它成功启动。
service_name="nginx"
until systemctl is-active --quiet "$service_name"
do
# 每隔 2 秒检查一次
sleep 2
echo "正在等待 $service_name 启动..."
done
echo "$service_name 已成功启动!"
这里的 systemctl is-active --quiet nginx 会返回 0(成功)表示服务正在运行,非 0 表示未运行。until 循环会持续执行,直到这个命令返回 0,也就是服务启动成功。
另一个实际应用是等待某个文件被创建:
target_file="/tmp/data.json"
until [ -f "$target_file" ]
do
sleep 1
echo "等待 $target_file 出现..."
done
echo "文件 $target_file 已创建,继续处理..."
这在自动化部署或数据同步流程中非常有用。你不需要知道文件什么时候生成,只需告诉脚本“等它出现”,它就会自动等待。
循环控制:break 与 continue 的妙用
在实际使用中,我们常常需要在循环过程中“提前退出”或“跳过当前迭代”。这时,break 和 continue 就派上用场了。
break:立即退出整个循环。continue:跳过当前这次循环,直接进入下一次。
举个例子:遍历一个用户列表,但只想处理前 3 个用户。
users=("alice" "bob" "charlie" "david" "eve")
count=0
for user in "${users[@]}"
do
# 如果已经处理了 3 个,就退出循环
if [ $count -ge 3 ]; then
break
fi
# 处理当前用户
echo "正在处理用户:$user"
# 模拟处理时间
sleep 1
count=$((count + 1))
done
echo "处理完成,共处理了 $count 个用户"
在这个例子中,break 让程序在处理完前 3 个用户后立即停止,不再继续遍历剩余的用户。
再看 continue 的使用场景:你想处理所有用户,但跳过名字以 "d" 开头的用户。
for user in "${users[@]}"
do
# 如果用户名以 d 开头,跳过本次循环
if [[ "$user" == d* ]]; then
continue
fi
echo "正在处理用户:$user"
done
continue 让脚本跳过 david,不执行后面的命令,直接进入下一轮。
这两个控制语句让你的循环逻辑更灵活,能应对复杂的业务需求。
实战案例:批量处理日志文件
现在我们综合运用前面的知识,来完成一个真实项目中的任务:批量压缩并备份过去 7 天的日志文件。
假设日志文件按日期命名,格式为 app.log.2024-04-01,我们想把最近 7 天的文件打包成一个压缩包。
backup_dir="/backup/logs"
archive_name="logs_$(date +%Y%m%d).tar.gz"
mkdir -p "$backup_dir"
temp_list=$(mktemp)
for i in {1..7}
do
# 计算前 i 天的日期
date_str=$(date -d "$i days ago" +%Y-%m-%d)
# 构造日志文件名
log_file="app.log.$date_str"
# 如果文件存在,就加入列表
if [ -f "$log_file" ]; then
echo "$log_file" >> "$temp_list"
fi
done
if [ ! -s "$temp_list" ]; then
echo "未找到过去 7 天的日志文件,备份跳过。"
rm -f "$temp_list"
exit 0
fi
tar -czf "$backup_dir/$archive_name" -T "$temp_list"
echo "已成功备份 $(( $(wc -l < "$temp_list") )) 个日志文件到 $backup_dir/$archive_name"
rm -f "$temp_list"
这个脚本展示了 shell 循环的强大之处:
- 使用 for 循环生成日期
- 用 while 或 if 判断文件是否存在
- 使用 break 提前退出
- 结合
tar实现批量处理
它将原本可能需要手动操作的复杂任务,简化为一条命令就能完成。
结语
shell 循环不仅是命令行工具的“加速器”,更是自动化运维的基石。它让你从重复劳动中解放出来,把精力投入到更有价值的问题解决中。
无论是 for 的精准遍历、while 的持续监控,还是 until 的等待机制,每一种循环都有其适用场景。掌握它们,你就能写出高效、健壮的脚本,真正实现“一次编写,无限复用”。
记住,写脚本不是为了炫技,而是为了让机器替你干活。当你熟练运用 shell 循环后,你会发现,那些曾经让你头疼的重复任务,如今只需几行代码就能轻松搞定。