介绍
Field Notes 已经知道哪些资源损坏或缺失。现在,组织者需要一套修复流程:只获取这些资源,将它们的字节与独立参考值比较,并且仅在通过校验后才发布替换文件。来源既可能返回 HTTP 错误,也可能返回看似完全成功、实际却包含错误文档的 HTTP 响应。
本实验会独立准备自己的资源包。你需要掌握上一项实验中的 SHA256、保留完整行的读取、命令替换和完整文件名处理方法,但不需要上一台实验环境中的任何文件。Workspace 和 Download Desk 共用一个容器。原创合成 PDF 和 curl 均已准备好。用时由作者估计,包含阅读和错误恢复。
替换前先下载一个候选文件
在这一步中,你将在独立的练习目录中保存并校验一份真实的活动资料。
从已准备好处理 UTF-8 文本的 Workspace 开始。找不到相对文件名时,请使用下面的绝对路径。pwd 和 ls pack 能帮助你区分目录错误和文件名拼写错误。
cd /home/labex/project/download-desk
可信校验清单位于 /opt/labex/download-pack-state/expected.sha256。格式与第一项实验中相同:64 个小写十六进制字符、两个空格,以及一个完整的单层文件名。清单由独立来源提供,可读、非空,并以换行符结尾。名称使用可打印 ASCII 字符,可以包含空格;斜杠、反斜杠、CR、LF、制表符以及 .、.. 名称不在支持范围内。请使用父目录没有链接的真实目录。预期成员必须不存在,或是本地普通文件;链接和目录对象会被拒绝。不要修改参考清单或来源。SHA256 匹配只能证明字节与这份可信参考一致,不能证明发布者身份。
cat /opt/labex/download-pack-state/expected.sha256
准备好的 trial 目录用于练习,与组织者的 pack 分开。mktemp 根据末尾含有若干 X 字符的模板创建一个唯一的空文件,并输出其路径。把路径捕获到 candidate 后,可以在当前 Workspace 会话中继续使用。新连接可能丢失 shell 变量;若发生这种情况,请从头重做这段练习,而不是猜测旧变量的值。
candidate=$(mktemp trial/.incoming.XXXXXX)
curl 用于发送 HTTP 请求。这里各选项有不同作用:
--get发送 GET 请求;--data-urlencode 'name=Workshop card.txt'将文件名安全地编码为查询参数。引号保护 shell 参数,URL 编码则处理请求中的空格。--fail让 503 等 HTTP 错误产生非零退出状态,但无法识别 HTTP 200 响应中错误的内容。--silent --show-error隐藏进度,同时保留错误信息。--max-time 8将整个传输限制在八秒以内。--output "$candidate"只写入临时候选文件。127.0.0.1:8080是同一容器中准备好的本地来源;/good提供正确字节。
curl --fail --silent --show-error --get --data-urlencode 'name=Workshop card.txt' --max-time 8 --output "$candidate" http://127.0.0.1:8080/good
printf 'Download exit: %s\n' "$?"
应看到 Download exit: 0。如果 URL 输错,curl 可能报告连接失败或 HTTP 拒绝。修正上面的地址,用 rm -- "$candidate" 只删除当前候选文件,然后从 mktemp 重新开始。-- 表示选项结束,这个准确的变量值标识你自己的练习文件。
从可信校验清单中对应的行复制资料的预期摘要。它就是下面这个值,独立于候选文件:
expected=a9986fb03d5ca72e42753d0ddd11dce7794b0e5a8b253dd4cd356825ca6d0583
管道符 | 将一条命令的标准输出连接到下一条命令的标准输入。下面的 printf 构造一条指向候选文件的校验记录。sha256sum --check 读取这条记录,而 && 只在校验成功后运行 mv。mv -- 将已经验证的候选文件重命名为最终练习文件。重复练习时允许替换这个练习文件;组织者的选择性修复规则针对的是 pack。
printf '%s %s\n' "$expected" "$candidate" | sha256sum --check && mv -- "$candidate" "trial/Workshop card.txt"
应看到以 : OK 结尾的一行。如果出现 FAILED,不要移动候选文件:只删除该候选文件,并重新执行修正后的下载。现在可以读取已经校验过的资料:
cat "trial/Workshop card.txt"
REPAIR DESK: EAST HALL
你已将尚未验证的下载内容与最终文件名隔离。接下来,会把同样的处理顺序应用到整个资源包。
选择性修复并从失败中恢复
在这一步中,你将为修复流程设置三个参数,测试它的有效结果和失败保护边界。
从 Workspace 的 /home/labex/project/download-desk 开始。脚本调用约定为 bash repair-pack.sh MANIFEST SOURCE_URL PACK_DIRECTORY。这是一个独立 Bash 程序,只使用这些参数和已安装命令,不依赖相邻的辅助文件。它必须先根据实际字节对成员分类。匹配的成员不发起下载请求,并保留字节、模式、所有者和 mtime。无关的 pack/notes.txt 也受到同样保护。只有缺失或不匹配的成员可以下载,每次调用中每个成员至多下载一次。匹配的空文件也是正确文件。整个资源包完整后,第二次运行必须产生零次下载请求,并保持资源包的全部属性。
每个候选文件都位于目标文件旁边,在正常成功或已报告失败后删除。发布以单个文件为单位:如果后续成员失败,之前已验证的替换可以保留。失败或内容错误的候选文件不能替换目标。这不是整包事务,也不保证能应对并发写入、断电或所有进程终止情况。只使用课程所属路径,并确保没有并发编辑。
下面的解析器和摘要提取方式已在上一项实验中学习。这些新增语法用于保护替换边界:
[ -r "$manifest" ]检查可读性;[ -s "$manifest" ]要求清单非空。!对判断取反。||可以在测试之间表示“或”,也可以在前一命令失败时执行后面的失败分支。这里的&&表示“且”。花括号将相关测试组合在一起,不创建子 shell。[ -d "$pack" ]检查目录。[ -L "$target" ]也能检测悬空链接;-e检查目标是否存在,-f检查是否为普通文件。在mv之前拒绝链接和其他对象类型,可以避免跟随来源链接,或误把候选文件移进一个目录。temporary=将变量初始化为空。trap '...' EXIT注册脚本退出时的清理操作。单引号将展开推迟到实际清理时。-n表示变量非空;清理只删除本脚本创建的文件。rm -f在候选文件已经不存在时也会安静地继续;--表示选项结束。continue进入下一行清单。exit 1报告失败。>&2把说明发送到标准错误,而不是普通输出。成功重命名后清空temporary,可以防止清理触及已经发布的文件。
使用第一项实验中讲解的字面 here-document 方法,保存下面的完整脚本:
cat > repair-pack.sh <<'BASH'
#!/usr/bin/env bash
manifest=$1
base=$2
pack=$3
if [ ! -r "$manifest" ] || [ ! -s "$manifest" ]; then
printf 'A readable nonempty trusted manifest is required.\n' >&2
exit 1
fi
if [ ! -d "$pack" ] || [ -L "$pack" ]; then
printf 'A real pack directory is required.\n' >&2
exit 1
fi
temporary=
trap 'if [ -n "$temporary" ]; then rm -f -- "$temporary"; fi' EXIT
while IFS= read -r line; do
expected=${line:0:64}
name=${line:66}
target=$pack/$name
if [ -L "$target" ] || { [ -e "$target" ] && [ ! -f "$target" ]; }; then
printf 'Expected a regular file or absent member: %s\n' "$name" >&2
exit 1
fi
if [ -f "$target" ]; then
actual=$(sha256sum -- "$target")
actual=${actual%% *}
if [ "$actual" = "$expected" ]; then
printf 'Keep: %s\n' "$name"
continue
fi
fi
temporary=$(mktemp "$pack/.repair.XXXXXX") || exit 1
if ! curl --fail --silent --show-error --get --data-urlencode "name=$name" --max-time 8 --output "$temporary" "$base"; then
printf 'Download failed: %s\n' "$name" >&2
exit 1
fi
actual=$(sha256sum -- "$temporary")
actual=${actual%% *}
if [ "$actual" != "$expected" ]; then
printf 'Checksum mismatch: %s\n' "$name" >&2
exit 1
fi
mv -- "$temporary" "$target" || exit 1
temporary=
printf 'Repaired: %s\n' "$name"
done < "$manifest"
BASH
清理只接收准确的临时路径,不会删除其他人的文件。约定也允许使用其他安全的临时文件名或等效循环。检查关注行为,不评分这里的命令拼写或 Keep: 输出文字。
试用内容错误的成功 HTTP 响应
受控的 /html 端点以 HTTP 200 返回 HTML 错误页面。这是真实响应,不是模拟出来的 curl 状态。下面的命令预期失败,并且不会替换原有的损坏资料:
bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/html pack
printf 'Repair exit: %s\n' "$?"
应看到 Repair exit: 1 和 Checksum mismatch: Workshop card.txt。PDF 原本已经正确,因此被跳过。错误候选文件被删除;仅凭 HTTP 成功,不能授权发布。ls -a 会包含隐藏名称,因此可以确认没有遗留 .repair.* 候选文件:
ls -a pack
现在改用正确来源。脚本会修复资料和缺失的地图,同时保留 PDF、空致谢文件和个人笔记:
bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/good pack
(cd pack && sha256sum --check /opt/labex/download-pack-state/expected.sha256)
每个清单成员都应显示 OK。切换到 Download Desk,重新加载页面,打开 PDF 和地图。地图中标明东入口。选择 PDF 旁的 Download,再用浏览器阅读器打开保存的文件。这些操作消费的都是本地资源包,不会回到来源补取内容。PDF 使用固定页面,需要时请使用原生缩放控件。然后返回 Workspace。
再次运行而不重复下载
准备好的来源会为每次实际资源请求在 requests.jsonl 中写一行,包含来源进程身份,并在观察前刷新每个事件。wc -l < FILE 将文件重定向给命令并统计行数,不会请求资源。先读取计数,重新运行修复流程,再读取一次:
wc -l < /opt/labex/download-pack-state/requests.jsonl
bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/good pack
wc -l < /opt/labex/download-pack-state/requests.jsonl
两个数字必须相同。四个成员都应保留原样。即使最终摘要相同,把所有文件重新下载一遍也会浪费请求,并可能改变正确文件的模式或时间戳。
在完整资源包之外练习传输失败
mkdir -p 会在需要时创建练习目录,不会重置已有目录。保持组织者已经修复好的资源包不变:
mkdir -p trial/fault-test
受控的 /error 端点返回 HTTP 503。应看到提及 503 的 curl 错误,以及非零脚本状态:
bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/error trial/fault-test
printf 'HTTP-error exit: %s\n' "$?"
应看到 HTTP-error exit: 1。受控的 /partial 端点只发送声明的响应体的一部分,然后关闭连接。应看到 curl 错误 18,提示仍有未接收字节,并且同样不会发布候选文件:
bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/partial trial/fault-test
printf 'Partial-transfer exit: %s\n' "$?"
应看到 Partial-transfer exit: 1。连接失败、HTTP 拒绝和 HTTP 200 中的错误内容是不同的观察结果,但都会阻止尚未验证的替换。用同一个正确端点和相同参数恢复练习目录:
bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/good trial/fault-test
如果清单或目录路径拼错,脚本会说明所需对象并以非零状态退出。请检查 pwd、ls 和绝对参考路径后重试。如果已保存的脚本出现语法错误,用上面的完整 here-document 只替换 repair-pack.sh。不要为了修复输入错误而删除组织者的资源包。
最终检查会读取真实资源包和需要保留的对象,还会把你的原样脚本放到文件名不同的隔离资源包和受控 HTTP 故障中运行,其中包括已有的损坏目标和缺失但允许为空的成员。那些测试请求来自你的脚本,不是评分器代为下载以补齐证据。重复检查不会修复或重置你的实际工作。
总结
你下载了候选文件,在发布前检查了 SHA256,并编写了可复用的选择性修复脚本。真实 HTTP 错误、内容错误的成功响应和中断的响应体都没有进入最终文件。原本正确的文件和个人笔记得到保留,资源包完整后再次运行不需要任何下载。



