Shell脚本精读 · S06-03 | 条件与控制流综合:读 30 行脚本的判断链 模块S06 控制流篇号S06-03 / 42预计阅读55 分钟主线Bash必读三星· 读脚本能力核心篇文章目录本篇目标30 秒速览正文1. 读判断链的四步2. 样例脚本全文3. 分块读法3.1 骨架第 14 行3.2 选项解析第 618 行3.3 参数个数第 2021 行3.4 文件存在第 23 行3.5 类型分支第 2529 行— case 模式3.6 校验和链第 3141 行— 嵌套 if (( FORCE ))3.7 收尾第 4344 行4. 代入三组输入走读结果场景 A场景 B场景 C5. 判断链「形状」速查6. 第二段练习更短的判断链15 行7. 与 S04、S05 的对照表8. 读脚本时易漏的点读脚本检查清单练习练习 1场景走读样例脚本练习 2场景走读第 6 节短脚本练习 3找问题改错题练习 4画判断链练习 5改写练习 6读脚本选择题S06 模块小结下一篇预告本篇目标把S04S06串起来面对一段二三十行的 Bash 脚本能自上而下理清「参数怎么验、选项怎么解析、文件怎么判、失败往哪走」。会用本篇的读法步骤和判断链图谱独立回答「给定输入会执行哪条分支、退出码大概多少」。30 秒速览先找脚本入口三件套set -euo、usage/参数个数、case选项循环。if/case/||常叠在同一段里短路与多分支分工不同。读分支时标真/假 → 走 then 还是 else/下一elif不要按「看起来像」猜。[[模式、(( ))数值、[ -f ]文件混用时先分清每一条测的是什么。提前exit比深层嵌套更常见看到|| { ...; exit; }要当成一整条「失败即停」。正文1. 读判断链的四步步骤做什么1. 划块按空行/注释分成选项解析、参数校验、业务分支、清理2. 标入口谁决定「能不能继续」(( $# ))、[[ -f ]]、case *)3. 追退出每个exit、4. 代入用一组具体参数在纸上走一遍本篇练习核心下面用一段约 35 行、风格接近真实运维/发布脚本的例子逐步走读。2. 样例脚本全文#!/usr/bin/env bashset-euopipefailusage(){echousage:$0[-fv] artifact2;exit2;}VERBOSE0FORCE0while(($#0));docase$1in-h|--help)usage;;-v|--verbose)VERBOSE1;shift;;-f|--force)FORCE1;shift;;--)shift;break;;-*)echounknown option:$12;exit1;;*)break;;esacdone(($#1))||usageartifact$1[[-f$artifact]]||{echonot found:$artifact2;exit1;}case$artifactin*.tar.gz);;*.zip)echozip not supported2;exit1;;*)echounknown artifact type2;exit1;;esacif[[-f${artifact}.sha256]];thenifsha256sum-c${artifact}.sha256/dev/null21;thenechochecksum okelif((FORCE));thenechowarn: checksum failed (forced)2elseechochecksum failed2exit1fielse((FORCE))||{echomissing${artifact}.sha256 (use -f)2;exit1;}fi((VERBOSE))set-xechoready:$artifact建议先通读一遍再对照下面分块说明。3. 分块读法3.1 骨架第 14 行set-euopipefail任一命令非 0 可能直接退出少数例外S01-04。后面若写grep ... || true是在主动抵消set -e。usage以退出码2结束惯例用法错误。3.2 选项解析第 618 行while(($#0));docase$1in...esacdone分支行为-h|--help调usageexit 2-v/-f置标志位shift吃掉选项--shift后break后面参数留给位置参数-*)未知选项exit 1*)非选项break出循环读点case与while (( $# 0 ))配合shift只发生在选项分支里位置参数release.tar.gz遇*)时不 shift留在$1。3.3 参数个数第 2021 行(($#1))||usageartifact$1条件为假 → 执行usage退出 2。为真 → 继续恰好一个位置参数。3.4 文件存在第 23 行[[-f$artifact]]||{echonot found: ...;exit1;}[[ -f ]]成功 → 短路不进{ ... }。失败 → 打印并exit 1。等价于if[[!-f$artifact]];thenechonot found:$artifact2exit1fi3.5 类型分支第 2529 行—case模式case$artifactin*.tar.gz);;*.zip)...exit1;;*)...exit1;;esac只接受.tar.gz后缀Shell 模式不是正则。app.zip→ 第二支exit 1。app.bin→*)exit 1。3.6 校验和链第 3141 行— 嵌套if(( FORCE ))外层 sidecar 文件${artifact}.sha256是否存在。情况内层逻辑有.sha256sha256sum -c成功 → 打印 ok有校验失败(( FORCE ))为真 → 警告继续否则exit 1无.sha256(( FORCE ))为真 → 跳过否则exit 1读点内层if sha256sum -cif后面是命令看退出码。elif (( FORCE ))整数标志不是字符串1。else分支里的(( FORCE )) || { ... exit 1; }FORCE0 时失败并退出。3.7 收尾第 4344 行((VERBOSE))set-xechoready:$artifact仅当VERBOSE1时打开跟踪。能执行到这里 → 前面分支都没 exit脚本正常结束退出码 0来自最后echo成功。4. 代入三组输入走读结果场景 A./check.sh-vrelease.tar.gz# 且 release.tar.gz、release.tar.gz.sha256 存在校验通过阶段结果选项-v→ VERBOSE1release.tar.gz留在$1个数$#1✓文件-f✓case匹配*.tar.gz空分支继续校验sha256sum -c成功 →checksum ok结尾set -x执行 → 打印ready: release.tar.gz场景 B./check.sh-fbroken.tar.gz# broken.tar.gz 存在有 .sha256 但内容不匹配阶段结果FORCE1校验失败走elif (( FORCE ))→ 警告不 exit结尾打印ready: broken.tar.gz带警告场景 C./check.sh app.zip阶段结果case*.zip支 →exit 1到不了 checksum5. 判断链「形状」速查读脚本时常见结构见到可对号入座┌─────────────────────────────────────┐ │ (( $# )) / : ${1:?} 参数门禁 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ while case 选项解析 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ [[ -f ]] / [ -d ] 路径存在 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ case 模式 / [[ pat ]] 类型分支 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ if 命令 / if [[ ]] / if (( )) │ │ 业务逻辑 exit │ └─────────────────────────────────────┘形状示例含义门禁(( $# 1 )) || usage不满足就停存在则做[[ -f f ]] cmd成功才继续失败则停cmd || exit 1一条线收尾多值分发case $cmd in ... esac子命令/类型嵌套 if外层文件、内层命令退出码两层条件标志位(( FORCE ))放宽检查与-f选项对应6. 第二段练习更短的判断链15 行#!/usr/bin/env bashmode${1:-}file${2:-}if[[-z$mode||-z$file]];thenechousage:$0check|run file2exit2ficase$modeincheck)[[-r$file]]||exit1grep-q.$file||{echoempty;exit1;}echook;;run)[[-x$file]]exec$file||exit1;;*)echobad mode2exit1;;esac读点速记第 4 行一个[[里||S05-03两个-z任一为空则 usage。check[[ -r ]]与grep -q两道关grep无匹配 → 退出码 1 → 走|| { echo empty; exit 1 }。run[[ -x ]] exec不可执行则|| exit 1。*)模式不在列表 → exit 1。7. 与 S04、S05 的对照表你看到的代码模块问什么cmd1 cmd2S04-02cmd1 失败时 cmd2 跑不跑cmd || exit 1S04-02失败路径是否终止脚本[ -f $f ]S05-01/02测的是文件还是字符串[[ $x *.log ]]S05-03右侧有引号吗是模式还是字面(( $# 1 ))S05-04是数值比较还是字符串if grep -q ...S06-01测的是 grep 退出码不是输出case ... in *.gz)S06-02默认*在哪8. 读脚本时易漏的点现象说明set -e 管道管道中失败命令未必让脚本退出S09、S01-04if [ -f $f ]未引号空格路径会拆词S02-03[[ $x 10 ]]在[[里是重定向应(( x 10 ))case的*.log引号导致只匹配字面*.loggrep无匹配退出码 1 条件假不是「脚本坏了」嵌套fi数清楚层次或改扁平exit读脚本检查清单能否在 1 分钟内指出选项循环、参数个数检查、默认*)分支给定./script -f foo.tar.gz能否说出FORCE影响哪一段if后面是[/[[/((/grep中的哪一种|| exit/与set -e同时存在时失败会不会意外退出脚本正常结束路径上最后一道可能exit的条件是什么练习练习 1场景走读样例脚本对第 2 节全文脚本判断下列调用能否执行到echo ready: ...并写出关键退出码若提前退出。./check.sh无参数./check.sh -h./check.sh release.tar.gz仅有 tar.gz无.sha256./check.sh -f release.tar.gz同上无 sidecar./check.sh -v release.tar.gz有.sha256且校验失败无-f参考答案否 →usage退出码2。否 →usage退出码2。否 →missing ...sha256退出码1。是 → FORCE 跳过缺失 sidecar最后ready: ...退出码0。否 →checksum failed退出码1。练习 2场景走读第 6 节短脚本./short.sh check empty.txtempty.txt存在且零字节。输出与退出码参考答案grep -q .失败 → 执行{ echo empty; exit 1; }。输出一行empty退出码1。走不到echo ok。练习 3找问题改错题下面脚本意图至少一个参数第一个参数为clean时删除第二个参数指定的普通文件。#!/usr/bin/env bashset-eif[$#-lt1];thenexit1ficase$1inclean)if[-f$2];thenrm$2fi;;esac列出至少4 处问题并给出修改要点。参考答案[ $# -lt 1 ]→[ $# -lt 1 ]或(( $# 1 ))且应打印 usage。case $1→case $1 in。clean分支未检查$# 2$2可能为空。[ -f $2 ]→[ -f $2 ]rm $2→rm -- $2。仅clean无默认*)时其他子命令静默成功视需求加*)报错。set -e下rm失败会直接退出若需捕获应处理。练习 4画判断链用一句话描述第 2 节脚本从while case结束到echo ready之间必须经过哪三个「关卡」不要求画正式流程图。参考答案示例答案①(( $# 1 ))参数个数②[[ -f $artifact ]]文件存在③case类型为*.tar.gz④ 校验和逻辑存在则验、缺失则 FORCE。任写清三个连续关卡即可④ 可拆成两条。练习 5改写把 S06-01 练习里的start|stop|restartif/elif改成case一行一分支;;可接受。参考答案case$cmdinstart)echostarting;;stop)echostopping;;restart)echorestarting;;*)echousage:$0start|stop|restart2exit2;;esac练习 6读脚本选择题对第 2 节脚本已存在pkg.tar.gz与正确的pkg.tar.gz.sha256执行./check.sh-v-fpkg.tar.gz下列哪项正确A. 一定打印checksum okB. 一定执行set -xC. 若校验失败仍可能打印readyD.case会因-f匹配失败参考答案B、C对。A校验失败且 FORCE1 时走警告不一定打印 ok。BVERBOSE1 →(( VERBOSE )) set -x会执行。CFORCE 允许校验失败后继续。D-f在选项循环已消费不会进入case $artifact的模式匹配。S06 模块小结篇号主题读脚本时干什么S06-01if/elif/else认退出码驱动的分支与嵌套S06-02case认模式、S06-03综合把 S04S06 叠在一段里走读S05提供「条件怎么写」S06提供「条件怎么挂到控制流」本篇是S06 的结业练习S14-03会在更长真实脚本上做全模块复盘。下一篇预告S07-01《for循环列表、in与{1..10}》— 进入循环模块处理多个文件与列表。