Bash case 的三个结尾:停止匹配、直接贯穿与继续检查怎样区分
一个文件分类脚本想给 report.log 同时标记“日志”和“报告”,把 case 分支结尾改了一下,却又标上了“表格”。问题往往不在文件名,而在分支执行完之后选择了哪一种控制方式。Bash 的三个结尾长得接近,后续动作却完全不同。
双分号让本次 case 结束;分号加与号直接执行下一分支的命令列表,不检查那一分支的模式;双分号加与号则继续检查后续模式,只有匹配时才执行对应命令。要看懂差别,最好让“下一条模式不匹配”和“再下一条模式匹配”同时出现在实验里。
固定输入,只改变分支的结尾
将代码保存为 demo.sh,使用 Bash 四或更新版本执行 bash demo.sh。不要使用 sh 运行,也不要在不支持这两种扩展的旧 Bash 中尝试。示例只操作字符串,不访问文件;report.log 是待分类的名称,并不要求磁盘上真有这个文件。
AI概念示意图:以抽象物件说明本文主题,不代表真实界面或运行结果。
#!/usr/bin/env bash
set -eu
stop_case() {
local name=$1 out=''
case "$name" in
*.log) out+='log ' ;;
*.csv) out+='csv ' ;;
report.*) out+='named ' ;;
esac
printf '%s' "$out"
}
through_case() {
local name=$1 out=''
case "$name" in
*.log) out+='log ' ;&
*.csv) out+='csv ' ;;
report.*) out+='named ' ;;
esac
printf '%s' "$out"
}
retest_case() {
local name=$1 out=''
case "$name" in
*.log) out+='log ' ;;&
*.csv) out+='csv ' ;;&
report.*) out+='named ' ;;&
*) out+='any ' ;;
esac
printf '%s' "$out"
}
a=$(stop_case report.log)
b=$(through_case report.log)
c=$(retest_case report.log)
test "$a" = 'log '
test "$b" = 'log csv '
test "$c" = 'log named any '
printf 'stop=%s\nthrough=%s\nretest=%s\n' "$a" "$b" "$c"
case unknown in
*.log) false ;;
esac
rc=$?
test "$rc" -eq 0
printf 'no-match-status=%s\n' "$rc"三行结果要和分支逐一对上
stop 输出 log,表示第一个模式命中后立即结束;后面的 report.* 即使也能匹配,也不会再被检查。through 输出 log csv,说明第一条的 ;& 直接进入下一条命令,尽管 report.log 并不匹配 *.csv。下一条以 ;; 结束,所以 named 不会执行。
retest 输出 log named any。第一个 ;;& 要求继续测试,跳过不匹配的表格分支,随后匹配 report.*,再继续匹配星号分支。代码没有把三种符号藏在变量里动态执行,而是各写一个小函数,方便直接看清每个控制位置。
兜底分支不一定只在没有命中时运行
星号能匹配这里的任意名称。如果前面的分支使用 ;;&,即使已经成功分类,流程仍可能走到星号并再次执行。它适合“统一收尾”,却不自动等价于“完全没有匹配时才报错”。把拒绝逻辑直接写在那里,可能误拒绝已经正确处理的输入。
如果需要多标签分类,建议先收集标签,再在 case 结束后检查集合是否为空,把“分类”和“未识别”分开。若多个分支必须共同执行一段收尾,也可以将共同动作放在整个结构之后。这通常比让许多分支互相贯穿更容易审核,也降低后来调整顺序造成的影响。
最后一段还验证:没有任何模式匹配时,case 返回零。它不是天然的“找到匹配返回成功、没找到返回失败”判断器。需要以状态表示分类结果时,应由自己的函数在末尾明确返回约定值,或在确实处理成功的分支中设置可检查的标记。
模式语言和执行顺序都要进入测试
case 使用的是 shell 模式,不是正则表达式。文件扩展名分类应针对完整输入定义模式;路径中是否带目录、扩展名是否区分大小写,都应该提前约定。本文输入没有目录,匹配设置也采用常见默认状态,因此不要把这组样本扩大成所有环境下的文件类型判断。
加入新分支时,至少保留三个测试名称:只匹配第一条、只匹配后面的条目,以及能同时匹配多条的名称。再加一个完全不匹配的输入,检查兜底行为和返回状态。对于带 ;& 的分支,还要检查下一条即使不匹配也会执行的动作是否确实允许。
分支内如果未来加入复制、上传等操作,贯穿就可能造成重复动作。先让分类函数返回标签,再由调用方按明确规则执行一次所需操作,更容易验证。不要因为日志里出现了期望文字,就忽略同一轮是否还运行了另一条不该执行的分支。
三个结尾都可以正确表达需求,选择依据是下一步究竟要停止、无条件执行,还是重新判断。代码评审时把这一问题写在分支旁,比把符号当作装饰性语法更有帮助;验收则以真实输出顺序和动作次数为准。


