一、简介:为什么循环是 Shell 的“发动机”?
Shell 是 Linux 的胶水语言,循环结构负责“重复”——批量处理文件、轮询接口、守护进程、自动重试。
实际场景
一晚备份 500 个数据库 →
for循环拼接文件名等待数据库端口存活 →
while循环+sleep服务异常则一直重启 →
until循环探测成功才退出
掌握for / while / until的语法差异与最佳实践,是运维、DevOps、开发者的基本功,也是写“不翻车”脚本的分水岭。
二、核心概念:3 种循环一句话记住
| 循环 | 关键词 | 判断逻辑 | 典型场景 |
|---|---|---|---|
| for | 遍历列表 | 列表耗尽即结束 | 批量文件、固定次数 |
| while | 条件为真 | 入口判断,真→继续 | 轮询、守护、读取管道 |
| until | 条件为假 | 入口判断,假→继续 | 等待成功、自动重试 |
口诀:for 遍历,while 真继续,until 假继续。
三、环境准备:3 分钟搞定实验沙箱
系统
Ubuntu 20.04+/CentOS 8+/Debian 11+(Bash ≥ 4.3 即可)权限
普通用户可完成 90% 实验;文件系统演示需chmod +x实验目录
mkdir -p ~/bash-loop-lab && cd ~/bash-loop-lab确认 Bash
echo $BASH_VERSION # ≥ 4.3 支持关联数组、关联索引
四、实际案例与步骤:5 大关卡由浅入深
每个脚本均可直接复制,保存后
chmod +x xxx.sh && ./xxx.sh跑通。
4.1 for 循环:批量重命名 + 安全前缀
需求:把 200 个.log文件加上日期前缀,防止覆盖。
#!/usr/bin/env bash # file: 01-rename.sh set -euo pipefail date_prefix=$(date +%F) for file in *.log; do [[ -f $file ]] || continue # 空目录保护 new="${date_prefix}_${file}" echo "mv $file $new" mv "$file" "$new" done echo "批量重命名完成"技巧点
用
for file in *.log避免$(ls *.log)空格裂开的坑。continue跳过目录/空匹配,脚本鲁棒性↑。
4.2 C 风格 for:固定次数倒计时
需求:脚本升级前倒计时 5 秒,可中途取消。
#!/usr/bin/env bash # file: 02-countdown.sh for ((i=5;i>0;i--)); do printf '\r升级将在 %d 秒后开始 ... 按 Ctrl+C 取消' "$i" sleep 1 done printf '\r开始升级! \n'说明
双括号
(())内变量无需$前缀,支持++ -- +=。printf \r实现同一行动态刷新,界面友好。
4.3 while 循环:端口未就绪就等待
需求:数据库 3306 端口监听前一直重试,每 2 秒检测一次。
#!/usr/bin/env bash # file: 03-wait-port.sh HOST=127.0.0.1 PORT=3306 TIMEOUT=300 count=0 echo "等待 $HOST:$PORT 就绪 ..." while ! nc -z "$HOST" "$PORT"; do ((count++)) if [[ $count -gt $((TIMEOUT/2)) ]]; then echo "超时退出" >&2; exit 1 fi sleep 2 done echo "端口已就绪,继续执行业务 SQL ..."依赖
nc(nmap-ncat):sudo dnf/apt install -y nc
4.4 until 循环:服务异常则一直重启
需求:Spring Boot jar 异常退出后 5 秒自动重启,直到手动 Ctrl+C。
#!/usr/bin/env bash # file: 04-until-restart.sh JAR=myapp.jar LOG=app.log until java -jar "$JAR" >> "$LOG" 2>&1; do echo "$(date) 服务异常退出,5 秒后重启" >&2 sleep 5 done echo "$(date) 服务正常退出,不再重启"要点
until判断最后一条命令退出码,非 0 就继续循环。适合“成功即跳出”场景,逻辑比
while true更清晰。
4.5 嵌套循环:批量创建目录+文件
需求:生成proj1/{src,doc,test}共 3×3=9 个目录,并在每个目录放 README.md。
#!/usr/bin/env bash # file: 05-nested.sh projects=(proj1 proj2 proj3) folders=(src doc test) for p in "${projects[@]}"; do for f in "${folders[@]}"; do dir="$p/$f" mkdir -p "$dir" echo "# $dir" > "$dir/README.md" done done echo "项目骨架生成完成"技巧
用数组+双引号避免空格分割;嵌套层级直观。
4.6 循环读取文件:行内空格安全
需求:逐行读取ip.list(格式:IP 端口),检测连通性。
#!/usr/bin/env bash # file: 06-readfile.sh INPUT=ip.list # 方式1:while read 最安全 while IFS= read -r line; do [[ -z "$line" || "$line" == \#* ]] && continue # 跳过空行与注释 ip=${line% *} port=${line#* } if nc -z "$ip" "$port"; then echo "$ip:$port OK" else echo "$ip:$port FAIL" >&2 fi done < "$INPUT"对比
for i in $(cat file)会把行内空格拆成多词 →禁止。while read+IFS=保留原始行,100% 安全。
4.7 关联数组+for:按分组批量杀进程
需求:按服务名(nginx、php、redis)分组优雅重启。
#!/usr/bin/env bash # file: 07-kill-group.sh declare -A groups=( [web]="nginx php-fpm" [cache]="redis-server" ) for group in "${!groups[@]}"; do echo "=== 重启 $group ===" for proc in ${groups[$group]}; do pkill -USR2 "$proc" # 示例信号,可换 systemctl done done要求
Bash 4.0+ 支持关联数组;检查版本echo $BASH_VERSION。
五、常见问题与解答(FAQ)
| 问题 | 现象 | 解决 | |
|---|---|---|---|
Syntax error: bad for loop variable | Dash 执行 C 风格for ((;;)) | 指定#!/usr/bin/env bash | |
ls: cannot access '*.log': No such file or directory | 空目录展开 | 用 `for file in *.log; do [[ -f $file ]] | continue; ...` |
| 死循环 CPU 100% | while 内无 sleep | 加sleep 1或read -t 1 | |
| read 丢失最后一行 | 文件末尾无\n | `while read -r line | [[ -n $line ]]; do ... done` |
| 变量修改在子 shell 无效 | 管道后while | 用重定向< file或shopt -s lastpipe |
六、实践建议与最佳实践
set -euo pipefail 黄金三件套
让脚本在变量未定义、管道失败、循环退出码非 0 时立刻失败,避免“静默继续”。用数组+双引号代替
lsfiles=(*.log) for f in "${files[@]}"; do ...循环内打印进度
printf '\r处理中 %d/%d' "$i" "$total"超时退出
自带计数器或timeout命令,防止死循环。调试技巧
bash -x script.sh看每次循环变量值。set +x/set -x局部开关,减少刷屏。
性能优化
大量文件优先
find ... -print0 | while IFS= read -r -d '' file; do ...避免在循环里
cat | grep | awk三连,改用 Bash 内置参数替换。
七、总结:一张脑图带走全部要点
Bash 循环 ├─ for:列表遍历 / C 风格计数 ├─ while:条件真继续(轮询) ├─ until:条件假继续(重试) ├─ 嵌套:多级目录、多项目 ├─ read:行内空格安全 └─ 黄金习惯:引号 + -euo pipefail + 超时掌握for / while / until的语法差异、安全写法、调试技巧,你就拥有了:
批量自动化的瑞士军刀:文件改名、备份、部署脚本 10 行搞定。
服务守护的坚固盾牌:端口等待、异常重启、日志监控不再手写死循环。
跨平台兼容的移植指南:避开
$(ls)、for i in $(cat file)等经典陷阱。
立刻打开终端,把本文脚本复制到~/bash-loop-lab,跑一遍、改一遍、拆一遍,写属于自己的第一个健壮循环!祝你玩得开心,循环永不死。