Shell 脚本核心能力:for/while 循环与 case 分支,从语法到运维实战全解析
在 Linux 运维工作中自动化是提升效率的核心而 Shell 脚本就是实现自动化的基础工具。无论是批量管理用户、检测服务器状态还是编写服务控制脚本都离不开两类核心语法循环语句和分支语句。很多初学者刚接触 Shell 时会分不清 for 和 while 的适用场景也不知道什么时候该用 case 而不是 if。本文就从语法本质、适用场景到真实运维案例带你彻底掌握 for 循环、while 循环与 case 分支语句看完就能写出实用的自动化脚本。一、for 循环遍历式批量操作的首选for 循环是 Shell 中最常用的循环结构它的核心逻辑是 **「遍历取值列表逐个执行固定操作」**。当你有明确的待处理对象列表比如用户名清单、IP 地址列表、文件列表或者已知循环次数时for 循环是最简洁高效的选择。1. 基础语法结构bash运行for 变量名 in 取值列表 do 执行的命令序列 done执行逻辑脚本会依次把取值列表中的每个值赋值给变量每赋值一次就执行一次 do 和 done 之间的命令直到列表中的所有值遍历完毕循环结束。取值列表的来源非常灵活可以是直接写死的字符串、从文件读取的内容、命令执行的结果也可以是通配符匹配的文件名。2. 实战案例 1批量创建系统用户在运维中经常需要批量创建账号比如给部门员工统一开通服务器权限。我们可以把所有用户名写在一个文本文件里用 for 循环逐行读取并创建。实现步骤新建用户名清单文件users.txt每行一个用户名编写脚本读取文件内容作为遍历列表逐个创建用户并设置统一初始密码脚本代码bash运行#!/bin/bash # 批量添加用户脚本 USER_LIST$(cat /root/users.txt) for USERNAME in $USER_LIST do useradd $USERNAME # 通过管道给用户设置密码--stdin表示从标准输入读取密码 echo 123456 | passwd --stdin $USERNAME /dev/null echo 用户 $USERNAME 创建完成 done执行后就可以通过tail /etc/passwd查看新建的用户信息。3. 实战案例 2批量检测主机存活状态管理多台服务器时快速排查哪些主机在线是高频需求。我们可以把 IP 地址写入文件用 for 循环批量 ping 检测连通性。脚本代码bash运行#!/bin/bash # 批量主机存活检测脚本 IP_LIST$(cat /root/ipadds.txt) for IP in $IP_LIST do # -c 3 发送3个包-i 0.2 包间隔0.2秒-W 3 超时时间3秒 # 所有输出重定向到/dev/null不显示ping的过程信息 ping -c 3 -i 0.2 -W 3 $IP /dev/null # $? 表示上一条命令的退出状态码0表示执行成功主机存活 if [ $? -eq 0 ] ; then echo 主机 $IP 在线 else echo 主机 $IP 离线 fi done这个脚本结合了 for 循环和 if 分支既能批量遍历又能根据结果输出不同状态是运维中非常实用的小工具。拓展类 C 风格的 for 循环除了遍历列表Shell 还支持类似 C 语言的计数循环适合固定次数的递增 / 递减场景bash运行for ((i1; i20; i)) do echo 当前序号$i done二、while 循环条件驱动的灵活循环和 for 循环的「遍历列表」不同while 循环的核心是 **「条件判断」**只要条件成立就反复执行循环体直到条件不成立时退出。它更适合循环次数不确定、需要根据条件动态控制的场景。1. 基础语法结构bash运行while 条件测试表达式 do 执行的命令序列 done执行逻辑先判断条件是否成立如果成立就执行循环体执行完后再次判断条件重复这个过程直到条件不成立循环结束。2. 实战案例 1按序号批量创建用户如果需要创建有规律编号的用户比如 stu1、stu2……stu20用 while 循环配合计数器就非常合适。脚本代码bash运行#!/bin/bash # 按序号批量创建用户 PREFIXstu i1 # 循环条件序号小于等于20 while [ $i -le 20 ] do useradd ${PREFIX}$i echo 123456 | passwd --stdin ${PREFIX}$i /dev/null # 计数器自增必须有这一步否则会陷入死循环 let i done echo 共创建了 $((i-1)) 个用户注意while 循环最容易踩的坑就是死循环。一定要确保循环体内有改变条件的语句比如计数器递增否则循环会永远执行下去占用系统资源。3. 实战案例 2交互式猜数字游戏while 循环的经典用法是实现交互式程序比如猜价格游戏脚本生成随机数用户反复猜测脚本给出提示直到猜中为止。这种不知道循环次数的场景用while true实现死循环猜中后用exit退出是最常用的写法。脚本代码bash运行#!/bin/bash # 猜数字游戏 # RANDOM是Shell内置变量生成0-32767的随机整数取模1000得到0-999的数 PRICE$(( RANDOM % 1000 )) TIMES0 echo 商品价格在0-999之间快来猜猜看吧 # while true 表示无限循环直到遇到exit或break退出 while true do read -p 请输入你猜测的数字 GUESS let TIMES if [ $GUESS -eq $PRICE ] ; then echo 恭喜你猜对了实际价格是 $PRICE echo 你一共猜了 $TIMES 次 # 猜中后退出脚本结束循环 exit 0 elif [ $GUESS -gt $PRICE ] ; then echo 太高了再试试 else echo 太低了再试试 fi done这个案例综合了 while 循环、if 多分支、内置变量、交互式输入非常适合练习 Shell 逻辑。拓展while 逐行读取文件处理文本内容时while 配合 read 命令可以逐行读取文件比 for 循环更稳定不会因为行内空格拆分bash运行while read line do echo 读取到行内容$line done 文件名三、for vs while两种循环该怎么选很多初学者会疑惑批量创建用户两种循环都能实现到底该用哪个其实核心区别在于循环的驱动方式表格对比维度for 循环while 循环核心逻辑遍历取值列表列表遍历完就结束条件判断驱动条件成立就循环适用场景已知循环次数、有明确的待处理列表循环次数不确定、需要动态条件控制典型场景批量处理文件、用户、IP 列表交互式程序、逐行读文件、条件监控写法特点简洁直观不需要手动维护计数器灵活可控需要手动控制循环退出条件简单总结有明确列表用 for条件控制用 while。四、case 分支多选项匹配的结构化方案说完循环我们再来看分支语句。大家都熟悉 if 分支但当有多个固定选项需要判断时比如服务脚本的 start/stop/restart写多层 if 会让代码很乱这时 case 分支就是更好的选择。case 分支的核心是 **「变量值与多个模式匹配匹配到哪个就执行对应命令」**结构清晰可读性强非常适合离散的固定选项判断。1. 基础语法结构bash运行case 变量值 in 模式1) 命令序列1 ;; 模式2) 命令序列2 ;; *) 默认命令序列所有模式都不匹配时执行 ;; esac语法说明;;表示当前分支结束相当于其他语言的 break*)是默认匹配项所有模式都没匹配到时执行模式支持通配符还可以用|表示「或」比如start|begin)2. 实战案例 1输入字符类型识别判断用户输入的字符是字母、数字还是其他字符用 case 的正则模式匹配就非常简洁。脚本代码bash运行#!/bin/bash # 字符类型识别 read -p 请输入一个字符按回车确认 INPUT_CHAR case $INPUT_CHAR in # 匹配大小写字母|表示或的关系 [a-z]|[A-Z]) echo 你输入的是字母 ;; # 匹配0-9的数字 [0-9]) echo 你输入的是数字 ;; # 其他所有情况 *) echo 你输入的是空格、功能键或其他特殊字符 ;; esac注意变量建议用双引号包裹避免输入空格等特殊字符时出现语法错误。3. 实战案例 2编写系统服务控制脚本在 Linux 中/etc/init.d/下的服务脚本基本都是用 case 分支实现的通过传入start/stop/restart参数来控制服务状态。我们来写一个简易的服务脚本模板bash运行#!/bin/bash # chkconfig: - 90 10 # description: 自定义服务控制脚本 # $1是脚本的第一个位置参数也就是执行脚本时传入的start/stop等 case $1 in start) echo 正在启动自定义服务... # 这里写启动服务的实际命令 echo 服务启动成功 ;; stop) echo 正在停止自定义服务... # 这里写停止服务的实际命令 echo 服务停止成功 ;; restart) # 复用stop和start逻辑 $0 stop $0 start ;; *) # 参数错误时提示用法 echo 用法: $0 {start|stop|restart} exit 1 ;; esac代码说明# chkconfig: - 90 10是 chkconfig 的识别配置分别表示默认运行级别、启动优先级、停止优先级$0表示脚本本身的文件名把脚本放到/etc/init.d/目录下就可以用chkconfig --add命令添加为系统服务实现开机自启管理case vs if 多分支怎么选if 分支适合范围判断、复杂逻辑组合比如同时判断多个条件灵活性更高case 分支适合固定值、离散选项的匹配结构更清晰可读性和可维护性更好尤其是选项超过 3 个时优势明显五、综合实战网段 MAC 地址采集脚本最后我们来做一个综合案例把 while 循环、if 分支、Linux 命令结合起来写一个 MAC 地址采集脚本扫描整个网段的在线设备记录 IP 和对应的 MAC 地址。这个脚本在运维中很实用比如做资产统计、IP-MAC 绑定配置时都能用到。脚本代码bash运行#!/bin/bash # 网段MAC地址采集脚本 NETWORK192.168.0. OUTPUT_FILE/etc/ethers # 如果输出文件已存在先备份旧文件 [ -f $OUTPUT_FILE ] cp -f $OUTPUT_FILE $OUTPUT_FILE.old HOST_ID1 # 遍历1到253的主机位 while [ $HOST_ID -lt 254 ] do CURRENT_IP${NETWORK}${HOST_ID} # arping发送2个包超时1秒检测主机是否在线 arping -c 2 -w 1 $CURRENT_IP /dev/null # 如果命令执行成功说明主机在线提取MAC地址写入文件 if [ $? -eq 0 ] then arp -n | grep $CURRENT_IP | awk {print $1,$3} $OUTPUT_FILE echo 采集到 $CURRENT_IP 的MAC地址 fi let HOST_ID done echo 采集完成结果已保存到 $OUTPUT_FILE执行脚本前需要先安装net-tools工具包yum install -y net-tools因为 arping 和 arp 命令都在这个包里。六、Shell 循环与分支编写最佳实践最后分享几个实用的编写技巧帮你写出更规范、高效、不易出错的 Shell 脚本变量引用加双引号引用变量时尽量用$VAR的形式避免变量值包含空格、特殊字符时导致语法错误。循环体尽量减少外部命令调用Shell 循环本身效率不高如果循环里反复调用 grep、awk 等外部命令大数量下性能会很差。能在循环外处理的就不要放到循环里。while 循环务必设置退出条件永远不要写没有退出逻辑的while true除非你明确知道自己在做什么。计数器递增、break/exit 退出至少要有一种退出方式。多固定选项优先用 case当判断分支超过 3 个且都是固定值匹配时优先用 case 而不是 if-elif代码可读性会高很多。脚本加注释和状态输出关键逻辑加注释批量操作加进度 / 状态输出不仅方便别人阅读自己过一段时间再看也能快速理解。循环和分支是 Shell 脚本的「骨架」所有复杂的自动化逻辑都是由这两类语法组合而成的。for 循环处理批量遍历while 循环处理条件控制case 分支处理多选项判断这三个语法掌握了就能解决运维中绝大多数自动化需求。当然Shell 编程光看没用一定要动手写、动手改。建议大家把文中的案例都自己敲一遍运行试试再试着修改参数、增加功能比如给批量用户脚本加判断用户已存在就跳过给检测脚本加日志输出在实践中才能真正掌握。