CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Shell 与 Bash 入门/课时/条件与循环
RawBlame
课时目录12 节

Shell 与 Bash 入门

  1. 01Shell 是什么
  2. 02目录与文件导航
  3. 03文件操作与权限
  4. 04管道与文本处理
  5. 05变量与字符串
  6. 06条件与循环
  7. 07函数与脚本结构
  8. 08正则表达式
  9. 09进程与定时任务
  10. 10网络命令
  11. 11系统管理与编辑器
  12. 12综合练习

条件与循环

if/for/while、exit code、set -e。

35 分钟

条件与循环

这一课你将能做到

  1. 用 if / [[ ]] / test 写出文件判断、数值比较、字符串比较三类条件,并解释 [ ] 与 [[ ]] 的区别
  2. 用 for / while 循环批量处理服务器列表和文件列表,用 case 写出带子命令的脚本骨架
  3. 组合条件与循环,写出一个「循环检测所有节点健康状态」的运维脚本

为什么要学

脚本没学到条件与循环,就只是「命令的录像回放」;学会它们,脚本才开始「思考」:部署前先判断旧进程在不在、重启后循环探测健康检查直到成功、对三台服务器挨个执行同一个检查。后端面试里「写个脚本批量检测服务是否存活」是真实出现过的手写题,本课结束时你能直接默写出来。

人话:if 是「路口」,根据路况决定走哪条道;for/while 是「传送带」,把一批东西挨个送进同一台机器。运维日常的「批量」二字,全靠这两个语法撑着。

先建立图像

条件与循环的执行流:

        ┌─────────┐
        │ 检测条件 │   [ -f app.jar ] / $count -gt 0 / ...
        └────┬────┘
        真   │   假
      ┌─────┴─────┐
      ▼           ▼
  干这件事      干那件事(或跳过)

for host in web1 web2 web3      ← 传送带 1:已知名单
while ! curl 健康检查            ← 传送带 2:条件满足前一直转

判断条件速查表(先混个脸熟,跟着做里逐个练):

写法含义
-f 文件是普通文件吗
-d 路径是目录吗
-z "$s"字符串为空吗
$a -eq $b数值相等(还有 -ne -gt -lt -ge -le)
"$a" = "$b"字符串相等
[[ $s == prod ]]通配匹配(仅 [[ ]])

跟着做

步骤 1:if 三种形态

#!/bin/bash
# 单分支:文件存在才备份
if [ -f /opt/myapp/app.jar ]; then
  echo "发现 jar 包,开始备份"
fi

# 双分支
count=$(who | wc -l)
if [ "$count" -gt 3 ]; then
  echo "在线用户偏多:$count"
else
  echo "在线用户正常:$count"
fi

# 多分支
score=75
if [ "$score" -ge 90 ]; then
  echo "优秀"
elif [ "$score" -ge 60 ]; then
  echo "及格"
else
  echo "不及格"
fi

语法三铁律:if 和 then 要换行或分号;[ ] 内侧必须有空格;fi 收尾。

步骤 2:[ ] 与 [[ ]] 的差别

s="hello world"

# [ ](POSIX test):含空格必须加引号,不支持通配和 && ||
[ "$s" = "hello world" ] && echo "相等"

# [[ ]](bash 扩展):不用引号也不拆词,支持通配和正则
[[ $s == hello* ]] && echo "以 hello 开头"
[[ "$s" == *world* ]] && echo "包含 world"
[[ $s =~ ^hello ]] && echo "正则匹配"

经验法则:日常写 bash 脚本直接用 [[ ]](更宽容、功能强);追求跨 sh 兼容的脚本才用 [ ]。

步骤 3:数值与字符串比较的坑

a=10
b=9
[ "$a" -gt "$b" ] && echo "10 > 9"        # 数值用 -gt
[ "10" \> "9" ] 2>/dev/null || echo "字符串比较 10 < 9!"  # 字符串序里 "10" < "9"

str=""
[ -z "$str" ] && echo "str 是空串"
[ -n "$str" ] || echo "str 不是非空"

记忆口诀:数值比较用字母缩写(eq/ne/gt/lt),字符串比较用符号(=、!=),两者别混用。

步骤 4:for 循环两种形态

# 形态 1:列表遍历(最常用)
for host in 192.168.1.10 192.168.1.11 192.168.1.12; do
  echo "ping $host"
  ping -c 1 -W 1 "$host" > /dev/null 2>&1 && echo "$host 存活" || echo "$host 不通"
done

# 形态 2:C 风格计数
for ((i=1; i<=5; i++)); do
  echo "第 $i 次探测"
done

# 遍历文件列表(注意引号防空格路径)
for f in /opt/myapp/logs/*.log; do
  echo "$f : $(wc -l < "$f") 行"
done

第一个例子已经是一个「批量存活检测」雏形,第 12 课会扩展成正式脚本。

步骤 5:while 循环与健康检查轮询

# 经典组合:读文件逐行处理
cat > hosts.txt << 'EOF'
192.168.1.10
192.168.1.11
EOF
while read -r ip; do
  echo "处理节点:$ip"
done < hosts.txt

# 重启后轮询健康检查:最多等 30 秒
for i in $(seq 1 30); do
  code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/health)
  if [ "$code" = "200" ]; then
    echo "服务已就绪(等了 ${i} 秒)"; break
  fi
  sleep 1
done

while read -r 是 Shell 界处理「按行文本」的标准姿势;重发探测 + break 是发布脚本的灵魂段落,务必练熟。

步骤 6:case 写脚本骨架

cat > appctl.sh << 'EOF'
#!/bin/bash
case "$1" in
  start)   echo "启动 $2 环境..." ;;
  stop)    echo "停止 $2 环境..." ;;
  restart) echo "重启 $2 环境..." ;;
  status)  echo "查看 $2 状态..." ;;
  *)       echo "用法:$0 {start|stop|restart|status} <环境>"; exit 1 ;;
esac
EOF
chmod +x appctl.sh
./appctl.sh status prod
./appctl.sh foo        # 看看 *) 分支的报错提示

case 比一长串 if-elif 清爽得多,Nginx、MySQL 的启动脚本内部就是这种结构。

讲清楚要点

  1. [ ] 是命令(test 的别名),[[ ]] 是 bash 关键字:命令就有「空格、引号、转义」的规矩;关键字由 bash 直接解析,支持 == 通配、=~ 正则、&&/|| 连接。bash 脚本首选 [[ ]]。
  2. 数值与字符串比较分两套符号:数值 -eq -ne -gt -ge -lt -le;字符串 = != < >(后者在 [ ] 里要转义)。拿字符串符号比数字会得到字典序的诡异结果("9" > "10")。
  3. && 和 || 是短路径的 if:cmd && echo ok || echo fail 一行搞定二分支,读脚本时按「成功则…,失败则…」理解。但注意:echo ok 自身失败也会触发 fail,关键逻辑别依赖这个细节,老老实实写 if。
  4. for 的列表在遍历前已确定:for f in *.log 是 Shell 先展开通配符再循环,循环里新建的 .log 文件不会进入本轮。要边处理边生成清单的场景用 while read。
  5. break 和 continue 控制循环走向:break 跳出整层循环,continue 跳过本轮。健康检查里「成功就 break、失败就 sleep 后继续」是标准模式。
  6. exit N 决定脚本的退出码:主流程最后一条命令的退出码就是脚本退出码,关键分支显式 exit 0/exit 1,让 crontab 和 CI 能判断你脚本的成败。

你可能会懵的地方

  • if [ ... ]; then 的分号能省吗? 换行能省,同一行不能省。if [ ... ]\nthen 和 if [ ... ]; then 等价,if [ ... ] then 会把 then 当 test 的参数,报错。

  • 为什么条件里变量要写 "$var"? [ ] 按参数个数判断语法,变量为空时 [ $var = x ] 只剩 [ = x ],直接语法错误;加引号后 "" 算一个合法空参数。[[ ]] 没这个问题,但养成引号习惯更稳。

  • seq 1 30 是什么? 生成 1 到 30 的数字序列,bash 也可以写 {1..30}。轮询上限用这种方式控制,避免脚本死循环——crontab 里死循环脚本是生产事故。

  • 怎么同时判断多个条件? [[ ]] 里用 &&、||:[[ -f app.jar && -d logs ]]。[ ] 里用 -a、-o 或多个 [ ] 用 && 连接。现代脚本用前者。

  • while read 为什么常写成 read -r? -r 禁止反斜杠转义,保留原始内容。没有它,日志里的 \n、\t 字面量会被 read 吃掉变形。这是行业默认写法。

错法对照

错法问题正解
if[$# -eq 0]if 和 [ 之间、[ 内侧都要空格if [ $# -eq 0 ]; then
[ $a > $b ] 比数值> 被当重定向,建了个名为 b 的文件数值用 -gt,或 ((a > b))
for x in "$@" 写成 in $*含空格参数被拆坏for x in "$@"
while 循环里改循环外变量管道右侧是子 Shell,改动丢失用 while read < 文件 或进程替换
for f in $(ls *.log)文件名带空格就断行错乱直接 for f in *.log

卡点急救

  1. 报 unary operator expected 或 integer expression expected:前者是变量为空导致 test 缺参数,加双引号 "$var";后者是拿字符串符号比了非数字,先确认值确实是数字或改用正确符号。
  2. unexpected EOF while looking for matching:if 没配 fi、for 没配 done、引号没闭合。用 bash -n script.sh 只做语法检查不执行,快速定位。
  3. 循环里 echo 的中文/变量没生效:检查是否在子 Shell(管道、here-string)里执行。把状态写进临时文件或用 while read < file 保持同进程。

面试常问

  • [ ]、[[ ]]、(( )) 的区别? [ ] 是 POSIX test 命令,兼容性最强但语法苛刻(空格、引号、转义);[[ ]] 是 bash 关键字,支持通配、正则、&&/||,推荐日常使用;(( )) 是算术运算,支持 ++、比较运算返回 0/1,专做数值逻辑。

  • 写一个脚本:轮询检测某服务健康接口,最多 60 秒,成功输出 OK 退出 0,超时输出 FAIL 退出 1。 参考实现:for i in $(seq 1 60); do code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/health); [ "$code" = "200" ] && { echo OK; exit 0; }; sleep 1; done; echo FAIL; exit 1。考点:curl 拿状态码、条件短路、break/exit、退出码约定。

  • while read line 遍历文件时最后无输出或不执行,可能是什么原因? 文件无换行结尾的最后一行 read 会读到但返回非 0 导致循环提前结束,可用 while read -r line || [ -n "$line" ] 兜底;或者是循环体里用了 ssh/读 stdin 的命令吃掉了输入,给这些命令加 < /dev/null。

自测三问

  1. 判断「文件存在且大小大于 0」,用 [[ ]] 怎么写?
  2. -eq 和 = 分别用在什么场景?
  3. 如何让 while read 循环里调用的 ssh 不吃掉剩余输入?

小结一句话

if 让脚本会判断、for/while 让脚本会批量、case 让脚本有子命令——三者齐备,你的脚本才从「命令录像」升级成「自动化工人」。

延伸

  • Bash 官方手册:Conditional Constructs
  • 下一课:函数与脚本结构——把重复逻辑封装起来,写出能维护的生产级脚本。
加载本节练习…
← 上一课变量与字符串下一课 →函数与脚本结构