Skip to content

统一示例项目(sample-project) ​

全知识库共享这一套可运行的最小项目,用于演示脚本结构、参数校验、错误处理、trap 清理、 标准输出与标准错误分离、无框架测试与 bats 测试,以及 POSIX 可移植性验证。各章节示例会复用其中的 函数库与约定,避免每篇重复搭建脚手架。

一、目录结构 ​

text
sample-project/
├── bin/
│   ├── greet.sh      # POSIX Shell 命令行入口(问候语生成器)
│   ├── stats.sh      # Bash 专用入口(从 stdin 统计整数)
│   ├── log-summary.sh # POSIX 入口(校验并聚合 TSV 日志)
│   └── retry.sh      # POSIX 入口(有限重试、退避与抖动)
├── lib/
│   └── common.sh     # 可复用函数库(日志、die、依赖检查、整数校验)
├── test/
│   ├── run-tests.sh  # 无框架测试入口(POSIX sh)
│   ├── greet.bats    # bats-core 测试示例
│   └── fixtures/
│       └── flaky     # 临时 PATH 命令桩
└── index.md          # 本文件

二、设计要点 ​

约定说明对应章节
数据走 stdout,日志走 stderr让管道与断言只处理“数据”,日志不污染结果08
set -eu(POSIX)/ set -euo pipefail(Bash)未定义变量即报错;Bash 额外让管道失败可见16
trap ... EXIT INT TERM HUP正常退出、错误退出、信号退出都清理临时目录10
mktemp -d 建隔离目录不用可预测的 /tmp/name.$$,避免竞态与冲突19
getopts 解析选项标准选项解析,配合 --/OPTIND05
while IFS= read -r 读输入不用 for x in $(...) 读任意文本13
${name} 花括号定界规避旧 Bash 在 UTF-8 下变量名边界解析问题(见下方“踩坑”)04

三、运行方法 ​

sh
# 1) POSIX 入口
sh bin/greet.sh 世界                 # 输出:你好,世界!
sh bin/greet.sh -n 2 -g Hello 世界   # 输出两行 Hello,世界!

# 2) Bash 入口(从 stdin 读整数)
printf '%s\n' 3 1 4 1 5 | bash bin/stats.sh   # count=5 sum=14 max=5

# 3) 日志汇总与有限重试
sh bin/log-summary.sh access.tsv
RETRY_JITTER_MAX=0 sh bin/retry.sh 3 0 command arg...

# 4) 无框架测试
sh test/run-tests.sh

# 5) bats 测试(需安装 bats-core ≥ 1.5)
bats test/greet.bats

四、验证记录(核验日期 2026-09-07) ​

验证环境:macOS 26.6.2(25G83,arm64),Locale zh_CN.UTF-8。

1、静态检查 ​

text
sh -n / bash -n              全部通过
shellcheck 0.11.0            --shell=sh / --shell=bash 全部通过(0 warning)
shfmt 3.14.1                 -i 4 -ci 规范,diff 为空

2、测试执行 ​

text
sh test/run-tests.sh         合计:通过 11,失败 0(exit=0)
bats test/greet.bats         1..6 全部 ok(exit=0)

3、POSIX 可移植性矩阵(greet.sh) ​

解释器版本结果退出码
dashmacOS /bin/dash你好,世界!0
bash --posix5.3.15你好,世界!0
ksh93u+ 2012-08-01你好,世界!0
sh(Bash 3.2.57)macOS /bin/sh你好,世界!0
bash5.3.15你好,世界!0

stats.sh 使用数组与 pipefail,仅在 Bash 上验证(3.2.57 与 5.3.15),不纳入 POSIX 矩阵。

五、踩坑指南(本项目实测) ​

坑 1:旧 Bash(3.2)在 UTF-8 下把全角标点并入变量名 ​

初版写 "...(name=$name)",在 macOS /bin/sh(Bash 3.2.57)下报 name�: unbound variable,因为它把紧跟 $name 的全角 ) 的首字节当成了变量名的一部分。

  • 治标:用 ${name} 显式定界变量名,让解析器不再吞掉后续字节。
  • 治本:需要精确文本时优先 printf 并把变量放进独立参数,而非拼进带 CJK 的字符串字面量。

坑 2:bats 默认把 stderr 合并进 $output ​

本项目日志写 stderr,若直接 run 再断言 $output 会因混入日志而失败。 用 run --separate-stderr(bats ≥ 1.5)让 $output 只含 stdout,并在 setup 中声明 bats_require_minimum_version 1.5.0 消除 BW02 警告。

坑 3:重试测试不能依赖真实网络和真实等待 ​

retry.sh 的测试通过临时 PATH 注入 test/fixtures/flaky:前两次固定返回 75,第三次成功; 同时设置 RETRY_JITTER_MAX=0、基础延迟为 0。这样测试能精确断言调用次数,且不受网络、随机数和时间影响。

六、关联章节 ​

检索关键词 ​

sample-project、Shell 示例项目、trap 清理、getopts、mktemp、bats、shellcheck、shfmt、可移植性矩阵。

ICP 备案号待补充 · Gitee 主页