ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Shell循环教程:for、while、until、break、continue详解

Shell循环教程:for、while、until、break、continue详解 批量改文件名、把日志按行读进分析、每 5 秒探一次端口直到它通——自动化脚本里只要重复的事就离不开循环。Shell 提供了for、while、until三种循环配合break和continue控制流程。这一篇把它们的写法、适用场景和最容易踩的分词坑讲清楚。一、for 循环遍历一组值基本写法for变量in值1 值2 值3do命令done#!/bin/bashfornameinAlice Bob Caroldoecho你好$namedone预期输出你好Alice 你好Bob 你好Carolin后面是一个列表shell 按空格把它切成若干项每轮把一项赋给name。注意列表里如果有带空格的词必须加引号forcityinNew YorkLos AngelesLondondoecho城市:$citydone预期输出城市: New York 城市: Los Angeles 城市: London不加引号时New York会被切成New和York两个元素这是 for 循环最常见的错误。用{a..b}生成数字序列foriin{1..5}doecho第$i次done预期输出第 1 次 第 2 次 第 3 次 第 4 次 第 5 次{1..100}是 bash 的大括号展开生成 1 到 100 的整数。想控制步长用{1..10..2}输出 1、3、5、7、9。C 风格的 for熟 C 语言的人会喜欢这种写法for((i1;i3;i))doechoi $idone预期输出i 1 i 2 i 3注意是双圆括号(( ))里面i可以直接用不用写i$((i1))。这种写法适合循环 N 次这种明确计数的场景。遍历命令的输出把命令替换的结果放进列表就能遍历文件、行内容#!/bin/bashforfilein*.logdoecho处理日志:$filedone假设当前目录有app.log、error.log、access.log预期输出处理日志: access.log 处理日志: app.log 处理日志: error.log更常用的是遍历ls、find的结果。但这里有个大坑文件名带空格时未加引号的for会把它拆碎。稳一点的做法是用find -print0配合while IFS read -r -d 后面讲 while 时会给完整示例。二、while 循环条件成立就一直转while条件do命令done和if用同一套条件写法条件退出码为 0 就继续循环。#!/bin/bashcount3while[$count-gt0]doecho倒计时:$countcount$((count-1))doneecho发射预期输出倒计时: 3 倒计时: 2 倒计时: 1 发射别忘了在循环体里改count否则条件永远成立脚本会变成死循环按CtrlC才能停。逐行读取文件这是 while 最实用的场景之一#!/bin/bashwhileIFSread-rlinedoecho读到一行:$linedone/etc/hostname假设/etc/hostname内容是web-server-01预期输出读到一行: web-server-01三个细节都不能少IFS防止行首尾空格被吃掉read -r防止反斜杠被转义最后的 文件把文件内容重定向进来。少一个某些行就会读错。等一个条件满足轮询端口#!/bin/bashecho等待数据库 5432 端口就绪...untilnc-z127.0.0.15432dosleep1echo还没好再等 1 秒doneecho数据库已就绪数据库启动需要 3 秒时预期输出等待数据库 5432 端口就绪... 还没好再等 1 秒 还没好再等 1 秒 还没好再等 1 秒 数据库已就绪这种等服务起来的脚本在容器启动、CI 流水线里天天用。三、until 循环条件成立就退出until和while正好相反条件不成立时循环条件一成立就退出。上面那个轮询例子用until写比while !更直观。循环继续循环的条件while condcond 退出码为 0until condcond 退出码非0#!/bin/bashn1until[$n-gt5]doechon $nn$((n1))done预期输出n 1 n 2 n 3 n 4 n 5实际项目里until用得比while少主要就是等待某个状态变成真这一类场景。绝大多数循环用while就够了。四、break 和 continuebreak立刻跳出整个循环#!/bin/bashforiin12345doif[$i-eq3];thenbreakfiechoi $idoneecho循环结束预期输出i 1 i 2 循环结束continue跳过本轮进入下一轮#!/bin/bashforiin12345doif[$i-eq3];thencontinuefiechoi $idone预期输出i 1 i 2 i 4 i 53 那一轮被 continue 跳过后面的 echo 没执行但循环继续了。嵌套循环时想跳多层break和continue默认只跳最内层。后面跟数字可以跳多层break2# 跳出两层循环continue2# 跳过外层当轮这个语法不常见读别人脚本时知道有这回事就行自己写时宁可把内层循环包成一个函数也别用break 2这种难读的写法。五、实战批量改名 统计把这一篇的知识凑成一个真实常用的脚本把当前目录所有.txt文件改名为.md并统计改了多少个。#!/bin/bashcount0forfin*.txtdo# 没有匹配文件时*.txt 本身会被当成字面量[-e$f]||continuemv$f${f%.txt}.mdcount$((count1))doneecho共重命名$count个文件假设目录里有note1.txt、note2.txt、readme.txt预期输出共重命名 3 个文件${f%.txt}是字符串截断把变量值末尾的.txt去掉这是 bash 参数展开的常用技巧。前面那行[ -e $f ] || continue是防御目录里没有任何.txt文件时*.txt不会展开f会是字面量*.txt这行检查把这种情况跳过。六、知识扩展for 为什么会把带空格的文件名拆碎这背后是 shell 的分词机制。当你写forfin*.txtshell 处理这行要走几步先做通配符展开*.txt变成note1.txt note with space.txt note2.txt然后做单词分词——按 IFS默认是空格、制表符、换行把这一长串切成若干独立单词最后才进入 for 循环。问题就出在分词那步note with space.txt里的空格被当成了分隔符结果它被切成了三个独立项note、with、space.txt。这就是为什么脚本批量处理带空格的文件经常出 bug。正确的通用姿势是用find输出以 null 结尾的文件名配合read -d 读find.-name*.txt-print0|whileIFSread-r-dfdoecho处理:$fdone-print0用\0字符分隔文件名read -d 按\0读取。因为文件名里不可能出现 null 字节这种组合对任意古怪文件名都安全。另一个相关的冷知识for循环里do后面那行能不能省略不能直接省略但可以写成for ...; dodo和for放一行用分号隔开——这就是为什么前面例子里 if 写成if ...; thenfor 写成for ...; do都是同一套规则。while、until、case、for的do/then/)后面都必须换行或加分号否则 shell 报语法错误。循环写多了你会发现90% 的 bash 脚本都是for 遍历一批文件while 读一行配置if 判断一下case 分流这四件套的组合。把这四件的语法记牢剩下的都是业务逻辑。
返回列表