【Linux从入门到精通】第25篇:循环结构——重复造轮子的终结者

目录

一、引言:为什么要学循环?

二、for循环:遍历的艺术

[2.1 列表遍历(最常用)](#2.1 列表遍历(最常用))

[2.2 实战:批量重命名文件](#2.2 实战:批量重命名文件)

[2.3 C语言风格的for循环](#2.3 C语言风格的for循环)

[2.4 seq命令的使用](#2.4 seq命令的使用)

三、while循环:条件驱动的重复

[3.1 基本语法](#3.1 基本语法)

[3.2 最经典的用法:逐行读取文件](#3.2 最经典的用法:逐行读取文件)

[3.3 实战:分析日志文件](#3.3 实战:分析日志文件)

[3.4 等待条件满足](#3.4 等待条件满足)

[3.5 无限循环](#3.5 无限循环)

四、until循环:反向条件的while

五、break与continue:流程控制

[5.1 break:跳出整个循环](#5.1 break:跳出整个循环)

[5.2 continue:跳过本次循环](#5.2 continue:跳过本次循环)

[5.3 break n:跳出多层循环](#5.3 break n:跳出多层循环)

六、综合实战:批量服务器巡检脚本

七、循环选择速查表

八、常见陷阱与注意事项

九、本篇小结

动手练习

十、下篇预告


一、引言:为什么要学循环?

想象以下场景:

  • 把100个.jpg文件全部重命名为.png

  • 检查50台服务器是否在线

  • 读取一个日志文件的每一行,提取包含"ERROR"的行

  • 等待数据库启动完成后再执行下一步操作

如果手动逐一处理,不仅低效,还容易出错。循环结构就是为解决这类重复性任务而生的。

Shell提供了三种循环:

  • for:已知循环次数,遍历一个列表

  • while:循环次数未知,满足条件就继续

  • until:循环次数未知,直到条件成立才停止

二、for循环:遍历的艺术

2.1 列表遍历(最常用)

bash

复制代码
for 变量 in 列表; do
    命令
done

最简单的例子

bash

复制代码
#!/bin/bash
for name in zhangsan lisi wangwu; do
    echo "Hello, ${name}!"
done

输出:

text

复制代码
Hello, 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}"
done

2.2 实战:批量重命名文件

bash

复制代码
#!/bin/bash
# 将当前目录下所有.txt文件重命名为.md

count=0
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语法:

bash

复制代码
for (( 初始值; 条件; 步进 )); do
    命令
done

bash

复制代码
#!/bin/bash
# 计算1到100的和
sum=0
for (( i=1; i<=100; 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支持)
start=1
end=100
for i in $(seq $start $end); do
    echo $i
done

seq与花括号的关键区别 :花括号必须在变量展开之前 被解释,所以{1..${end}}会失败;而seq支持动态参数。当范围边界是变量时,必须用seq

三、while循环:条件驱动的重复

3.1 基本语法

bash

复制代码
while 条件; 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_count=0
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_wait=30
waited=0

echo "等待MySQL服务启动..."

while ! mysqladmin ping -u root --silent 2>/dev/null; do
    sleep 2
    ((waited+=2))
    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 "按Ctrl+C退出"
    sleep 1
done

四、until循环:反向条件的while

untilwhile是镜像关系------until在条件为假 时继续循环,条件变真时退出。

bash

复制代码
until 条件; do
    命令
done

bash

复制代码
#!/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
done

5.2 continue:跳过本次循环

bash

复制代码
#!/bin/bash
# 只处理数字命名的文件,跳过其他
for file in *; do
    if [[ ! "$file" =~ ^[0-9]+$ ]]; then
        continue   # 不是纯数字,跳过
    fi
    echo "处理数字文件: ${file}"
done

5.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_FILE="server_check_$(date +%Y%m%d).log"

echo "=== 服务器巡检 $(date) ===" | tee "$LOG_FILE"

total=${#SERVERS[@]}
online=0
offline=0

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(字符串原样返回,而不是跳过循环)

解决方案

bash

复制代码
shopt -s nullglob   # 启用:无匹配时返回空
for file in *.log; do
    [ -f "$file" ] && echo "$file"
done

陷阱二:管道会创建子Shell,循环内的变量修改会丢失

bash

复制代码
count=0
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 ... in for var in list; do ... done 遍历列表、文件
for (()) for ((i=0; i<n; i++)); do ... done 精确次数控制
while while 条件; do ... done 逐行读文件、等待条件
until until 条件; 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在函数中的使用)

  • 返回值(return vs echo

  • 如何用source引入公共函数库

学会函数后,你将能编写出结构清晰、可复用的专业级Shell脚本。


延伸思考while IFS= read -r line是逐行读文件的金标准写法,但每个部分都有其用意。IFS=防止行首空格被吞,-r防止反斜杠转义,read line从标准输入读取一行。少一个参数都可能在某些边界情况下出错。这就是Shell编程的特点------看似随意,其实处处有讲究。

相关推荐
vortex52 小时前
守护开源世界的猎犬:ClamAV 软件包介绍
linux·网络安全
zzzyyy5382 小时前
基础IO(1)
linux·运维·数据库
zzzb1234562 小时前
WSL(Ubuntu)部署Nginx\+PHP8\.2完整教程(新手友好\+避坑指南)
linux·nginx·ubuntu·php
neo33012 小时前
debian MEDIATEK Corp. Device 7925 无线网卡驱动安装
运维·服务器·debian
想拿大厂offer2 小时前
【Linux】编辑器、IDE 与操作系统:Linux 开发工具链的哲学与实践
linux·ide·编辑器
其实防守也摸鱼2 小时前
网络安全与数据库运维核心知识点总结(附习题)
运维·网络·数据库·笔记·安全·web安全
面向对象World2 小时前
养虾从入门到放弃(Windows&Ubuntu)
linux·运维·ubuntu
Danileaf_Guo2 小时前
Ubuntu 26.04桌面版部署
linux·运维·服务器·ubuntu