Shell脚本的woes:Coursebook脚本编程安全课
【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebook
写 Shell 脚本,人人都会echo "hello",但"能跑"和"跑得安全"之间,隔着无数新手踩过的坑。Coursebook 是伊利诺伊大学开源的入门系统编程教材(Open Source Introductory Systems Programming Textbook),它最被低估的宝藏不是章节正文,而是 _scripts/ 目录里十几个构建脚本——从校验 TeX 日志到推送发布,整条流水线都由 Shell 脚本把关。这篇指南把其中最有代表性的5 堂脚本编程安全课拆给你看,新手照着检查,能避开 Shell 脚本最常见的翻车模式。
一、认识 Coursebook 的 Shell 脚本工具箱
整个仓库的自动化逻辑集中在几个文件里:
| 文件 | 角色 |
|---|---|
| Makefile | 构建 PDF / EPUB 的总入口 |
| order.yaml | 章节顺序清单,脚本据此逐章处理 |
| _scripts/install.sh | CI 安装阶段:锁定版本、装依赖 |
| _scripts/script.sh | CI 构建阶段:编译 + 逐项质量门禁 |
| _scripts/deploy.sh | 发布阶段:把产物推到部署分支 |
每个脚本的分工都能在 _scripts/Readme.md 里查到一句话说明——"脚本也要有文档",这本身就是好习惯。
二、第一课:用 set -e 让错误"当场倒下"
新手脚本最常见的 woes:某一步失败了,脚本却若无其事地跑到最后,产出一份看起来正常的坏结果。Coursebook 的所有脚本开头都有一句"保命符":
set -e/set -o errexit:任何命令失败,脚本立即退出,见 deploy.sh、script.shset -u:引用未定义变量直接报错,而不是悄悄输出空串,见 check_tex_logs.shset -o pipefail:管道中任何一环失败都算失败,见 compare_pdf_text.sh
更值得学的是"例外管理":确实允许失败的非致命步骤,会显式写成|| true并注释原因(如 site_deploy.sh#L27-L33)。快速失败是原则,例外必须写出来——而不是靠运气。
三、第二课:锁定版本 + SHA256 校验和,下载不裸奔
install.sh 安装 pandoc 时的写法,是依赖下载的教科书级示范:
PANDOC_VERSION=3.10.2 wget -q .../pandoc-${PANDOC_VERSION}-1-amd64.deb echo "${PANDOC_SHA256} ${PANDOC_DEB}" | sha256sum -c -三个要点,缺一不可:
- 钉死精确版本(3.10.2,而非 3.x),并在注释里写清"为什么是这个版本"
- 下载后先验 SHA256 校验和,文件被篡改或下错,当场拒绝安装
- 留好回滚方案:脚本注释里直接写明旧版本来源与恢复步骤(install.sh#L21-L23)
装 EPUBCheck 时同样先检查 Java 是否存在、只装最小 JRE(install.sh#L43-L47)——依赖越少,攻击面越小。
四、第三课:变量加引号,路径不裸奔
看 check_verapdf.sh#L13-L18 处理文件路径的方式:
dir=$(cd "$(dirname "$pdf")" && pwd) docker run --rm -v "$dir":/data "$VERAPDF_IMAGE" ... "/data/$base"每一处变量都包在双引号里。原因很朴素:文件名一旦含空格或特殊字符,不加引号的$pdf会被 shell 拆成多个参数,轻则逻辑错乱,重则执行到意料之外的命令。同理,push_to_wiki.sh 用mktemp -d生成随机临时目录而不是手写/tmp/wiki,既避免命名冲突,也避免别人抢先创建目录。
五、第四课:退出码是一份契约,重试要有限次
好的 Shell 脚本像一份 API 文档。compare_pdf_text.sh 明确约定:0= 通过、1= 内容不一致、2= 输入不可用;check_tex_logs.sh 则把"错误 / 警告 / 缺失字符"分开计数,只让真正致命的问题(TeX 错误、缺字)触发失败退出。
重试逻辑也要"有限且有退避",看 site_retry.sh#L3-L6:最多重试NUM_RETRIES次,失败时先清理(site_cleanup.sh)再 sleepBUILD_TIME秒,而不是死循环硬刷。
六、第五课:令牌与密钥的安全处理 🔒
发布阶段最需要小心密钥,Coursebook 的做法值得逐条抄作业:
- 令牌不进日志:用内置 token 拼 HTTPS 克隆地址,由 Actions 自动脱敏,并特意不 echo 含令牌的 URL(push_to_wiki.sh#L11-L16)
- 令牌即改即还原:deploy.sh 临时改写 origin 推送,推完立刻换回旧地址(deploy.sh#L47-L52)
- SSH 密钥最小化:
IdentitiesOnly=yes -F /dev/null让 ssh 只用指定的那一把钥匙(site_deploy.sh#L6) - 密钥缺失时优雅跳过:部署钥匙文件不存在就跳过而非报错,保证 fork 环境也能跑通(push_to_wiki.sh#L39-L43)
七、动手:本地跑一遍 Coursebook 构建流水线
仓库只读也能玩:克隆下来先读脚本、再跑构建,观察门禁如何工作。
git clone https://gitcode.com/GitHub_Trending/co/coursebook.git coursebook cd coursebook bash -n _scripts/install.sh # 语法检查,0 秒上手 make epub # 需要 pandoc;PDF 构建则需 TeX Live(见 Makefile)构建产物里的每一张章节图——比如这张 TCP 头部示意图——都必须带 alt 文本才能通过 alt_lint.py 和 epub_check 门禁;EPUB 构建时脚本还会自动把.eps转成.png。内容可访问性也是被脚本"验收"的,不只是排版问题。
关于标签化 PDF 构建的完整背景,可以读 docs/pdf-tagging-spike.md 了解整套门禁设计。
八、新手 Shell 脚本安全检查清单 ✅
| # | 检查项 | Coursebook 出处 |
|---|---|---|
| 1 | 开头有set -e,必要时加set -u -o pipefail | script.sh |
| 2 | 非致命步骤显式|| true并注释原因 | site_deploy.sh |
| 3 | 下载的依赖钉版本 + 验 SHA256 | install.sh#L24-L29 |
| 4 | 变量一律加双引号,临时目录用mktemp -d | push_to_wiki.sh#L9 |
| 5 | 退出码语义明确(0 通过 / 1 业务失败 / 2 环境错误) | compare_pdf_text.sh#L6-L10 |
| 6 | 重试有限次、有退避、先清理 | site_retry.sh |
| 7 | 令牌不进日志、用完即还原、密钥最小化 | deploy.sh#L47-L52 |
| 8 | 每个脚本都有一句话文档 | _scripts/Readme.md |
小结:Shell 脚本的 woes 大多不是语法问题,而是"失败时怎么办"的问题。Coursebook 用一套真实的开源教材构建流水线证明了——快速失败、锁定版本、加引号、讲退出码、管好密钥,五件事做好,脚本就能安全地跑在生产环境里。
【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考