目录一、引言为什么要学循环二、for循环遍历的艺术2.1 列表遍历最常用2.2 实战批量重命名文件2.3 C语言风格的for循环2.4 seq命令的使用三、while循环条件驱动的重复3.1 基本语法3.2 最经典的用法逐行读取文件3.3 实战分析日志文件3.4 等待条件满足3.5 无限循环四、until循环反向条件的while五、break与continue流程控制5.1 break跳出整个循环5.2 continue跳过本次循环5.3 break n跳出多层循环六、综合实战批量服务器巡检脚本七、循环选择速查表八、常见陷阱与注意事项九、本篇小结动手练习十、下篇预告一、引言为什么要学循环想象以下场景把100个.jpg文件全部重命名为.png检查50台服务器是否在线读取一个日志文件的每一行提取包含ERROR的行等待数据库启动完成后再执行下一步操作如果手动逐一处理不仅低效还容易出错。循环结构就是为解决这类重复性任务而生的。Shell提供了三种循环for已知循环次数遍历一个列表while循环次数未知满足条件就继续until循环次数未知直到条件成立才停止二、for循环遍历的艺术2.1 列表遍历最常用bashfor 变量 in 列表; do 命令 done最简单的例子bash#!/bin/bash for name in zhangsan lisi wangwu; do echo Hello, ${name}! done输出textHello, zhangsan! Hello, lisi! Hello, wangwu!列表的多种写法bash# 花括号展开数字序列 for i in {1..5}; do echo 第${i}次执行 done # 花括号展开带步长 for i in {0..10..2}; do echo $i # 0, 2, 4, 6, 8, 10 done # 花括号展开字母序列 for letter in {a..e}; do echo $letter done # 通配符展开遍历文件 for file in /var/log/*.log; do echo 处理文件: ${file} done # 命令替换遍历命令输出 for user in $(cat /etc/passwd | cut -d: -f1); do echo 用户: ${user} done2.2 实战批量重命名文件bash#!/bin/bash # 将当前目录下所有.txt文件重命名为.md count0 for file in *.txt; do # 如果没有.txt文件for会原样返回*.txt if [[ ! -f $file ]]; then echo 没有找到.txt文件 exit 0 fi # 用字符串操作去掉.txt后缀加上.md new_name${file%.txt}.md mv $file $new_name echo 重命名: ${file} → ${new_name} ((count)) done echo 共重命名 ${count} 个文件2.3 C语言风格的for循环Bash也支持类似C语言的for语法bashfor (( 初始值; 条件; 步进 )); do 命令 donebash#!/bin/bash # 计算1到100的和 sum0 for (( i1; i100; i )); do sum$((sum i)) done echo 1到100的和: ${sum} # 5050适用场景循环次数需要精确控制或步进逻辑较复杂时。2.4 seq命令的使用seq生成序列比花括号展开更灵活——支持变量代替硬编码数字bash# 基本用法 seq 1 5 # 1 2 3 4 5 seq 1 2 10 # 1 3 5 7 9步长2 seq -w 01 10 # 01 02 ... 10等宽补齐 # 在for中使用花括号不支持变量展开seq支持 start1 end100 for i in $(seq $start $end); do echo $i doneseq与花括号的关键区别花括号必须在变量展开之前被解释所以{1..${end}}会失败而seq支持动态参数。当范围边界是变量时必须用seq。三、while循环条件驱动的重复3.1 基本语法bashwhile 条件; do 命令 done条件为真时持续循环条件变假时退出。3.2 最经典的用法逐行读取文件bash#!/bin/bash # 逐行读取文件并处理 while IFS read -r line; do echo 读取到: ${line} done /etc/passwd参数解释IFS清空IFS保留行首行尾空格read -r-r禁止反斜杠转义原样读取 /etc/passwd输入重定向把文件内容喂给while循环3.3 实战分析日志文件假设有一个Nginx访问日志access.log提取所有返回500状态码的行bash#!/bin/bash error_count0 LOG_FILE/var/log/nginx/access.log if [[ ! -f $LOG_FILE ]]; then echo 日志文件不存在: ${LOG_FILE} exit 1 fi while IFS read -r line; do # 用字符串处理提取状态码日志中状态码通常在HTTP/1.1 后面 if [[ $line ~ \ 500\ ]]; then echo 500错误: ${line} ((error_count)) fi done $LOG_FILE echo 共发现 ${error_count} 条500错误3.4 等待条件满足bash#!/bin/bash # 等待MySQL服务就绪最多等待30秒 max_wait30 waited0 echo 等待MySQL服务启动... while ! mysqladmin ping -u root --silent 2/dev/null; do sleep 2 ((waited2)) echo 已等待 ${waited} 秒... if [[ $waited -ge $max_wait ]]; then echo 超时MySQL未在${max_wait}秒内启动 exit 1 fi done echo MySQL已就绪3.5 无限循环bash# 持续监控 while true; do df -h / | tail -1 sleep 5 done # 也可用 : 冒号是Shell内置的恒真命令 while :; do echo 按CtrlC退出 sleep 1 done四、until循环反向条件的whileuntil和while是镜像关系——until在条件为假时继续循环条件变真时退出。bashuntil 条件; do 命令 donebash#!/bin/bash # 等待服务停止 until ! systemctl is-active --quiet nginx; do echo Nginx仍在运行等待停止... sleep 2 done echo Nginx已停止五、break与continue流程控制5.1 break跳出整个循环bash#!/bin/bash # 查找第一个包含error的日志文件 for file in /var/log/*.log; do echo 检查: ${file} if grep -q error $file 2/dev/null; then echo 在 ${file} 中找到error break # 找到就停止搜索 fi done5.2 continue跳过本次循环bash#!/bin/bash # 只处理数字命名的文件跳过其他 for file in *; do if [[ ! $file ~ ^[0-9]$ ]]; then continue # 不是纯数字跳过 fi echo 处理数字文件: ${file} done5.3 break n跳出多层循环bash#!/bin/bash # 二维搜索 for i in {1..10}; do for j in {1..10}; do if [[ $((i * j)) -eq 56 ]]; then echo 找到: ${i} × ${j} 56 break 2 # 跳出两层循环 fi done done六、综合实战批量服务器巡检脚本把循环、条件判断、变量处理整合起来bash#!/bin/bash # 批量服务器巡检脚本 SERVERS( 192.168.1.10 192.168.1.20 192.168.1.30 10.0.0.5 ) LOG_FILEserver_check_$(date %Y%m%d).log echo 服务器巡检 $(date) | tee $LOG_FILE total${#SERVERS[]} online0 offline0 for server in ${SERVERS[]}; do # 用ping测试连通性ping 1次超时1秒 if ping -c 1 -W 1 $server /dev/null; then echo [✓] ${server} - 在线 | tee -a $LOG_FILE ((online)) else echo [✗] ${server} - 离线 | tee -a $LOG_FILE ((offline)) fi done echo | tee -a $LOG_FILE echo 巡检完成共${total}台在线${online}台离线${offline}台 | tee -a $LOG_FILE # 如果存在离线服务器退出码返回1便于集成到监控系统 if [[ $offline -gt 0 ]]; then exit 1 fi这个脚本可直接用于日常运维也可以集成到crontab中定时执行。七、循环选择速查表场景推荐循环原因遍历固定列表文件、数字、字符串for ... in简洁直观精确控制循环次数for ((...))类似C语法灵活逐行读取文件while read经典范式省内存等待某条件成立while条件控制灵活等待某条件不成立until语义清晰无限循环while true通用写法八、常见陷阱与注意事项陷阱一通配符在没有匹配文件时不展开bash# 如果当前目录没有.log文件 for file in *.log; do echo $file done # 结果输出*.log字符串原样返回而不是跳过循环解决方案bashshopt -s nullglob # 启用无匹配时返回空 for file in *.log; do [ -f $file ] echo $file done陷阱二管道会创建子Shell循环内的变量修改会丢失bashcount0 cat /etc/passwd | while read line; do ((count)) done echo $count # 输出0因为while在子Shell中运行变量修改丢失解决方案改用输入重定向done file或进程替换done (command)。陷阱三for循环读取文件会一次性加载所有内容到内存大文件不要用for line in $(cat huge_file)改用while read逐行处理。九、本篇小结三种循环语法循环语法适用场景for ... infor var in list; do ... done遍历列表、文件for (())for ((i0; in; i)); do ... done精确次数控制whilewhile 条件; do ... done逐行读文件、等待条件untiluntil 条件; do ... done反向while流程控制break跳出循环continue跳过本次循环break n跳出n层循环循环选择优先级text已知列表 → for ... in 已知次数 → for (()) 逐行处理 → while read 条件等待 → while/until动手练习bash#!/bin/bash # 练习统计每个文件的扩展名数量 declare -A ext_count # 关联数组需要Bash 4 for file in *; do if [[ -f $file ]]; then ext${file##*.} # 没有扩展名的文件 [[ $ext $file ]] ext(无扩展名) ((ext_count[$ext])) fi done echo 文件扩展名统计 for ext in ${!ext_count[]}; do echo .${ext}: ${ext_count[$ext]} 个 done十、下篇预告循环让脚本能批量处理但脚本本身也变得越来越长。如何把重复使用的代码块封装起来让脚本更模块化、更易维护下一篇我们将学习函数与模块化包括函数的定义与调用参数传递$1、$2在函数中的使用返回值returnvsecho如何用source引入公共函数库学会函数后你将能编写出结构清晰、可复用的专业级Shell脚本。延伸思考while IFS read -r line是逐行读文件的金标准写法但每个部分都有其用意。IFS防止行首空格被吞-r防止反斜杠转义read line从标准输入读取一行。少一个参数都可能在某些边界情况下出错。这就是Shell编程的特点——看似随意其实处处有讲究。