介绍
DAYMARK 是一家小咖啡馆。闭店时,团队下载销售 CSV 记入账本。计算失败不能覆盖上份完整文件。你将先理解总数,再完成简短的 Bash 发布包装脚本;计算程序已经提供。
只需基本终端操作能力,命令与 shell 语法会在使用前解释。从 Daymark 开始,它是端口 8081 上的真实网站;命令在已进入 /srv/daymark-sales 的 Workstation 中执行。数据是合成的,无需安装或外部账户。
理解并计算闭店汇总
在这一步,你将比较咖啡馆源订单与真实候选导出。从 Daymark 开始,打开 View source records ↗。四行源记录包含 6 份单价 300 分的 Espresso、3 份单价 250 分的 Tea,以及 2 份单价 450 分的 Cake:总共 11 项、3450 分。一分是一个货币单位的百分之一;使用整数分可以避免舍入歧义。同一商品的重复行需要合并。

打开 Reports 和 Download latest CSV ↓,保存真实 CSV 文件。CSV 表示逗号分隔值,首行给出列名。初始文件是上次闭店报表,包含 TOTAL,1,100。在完整替代文件出现前,它必须保持可用。选择 DAYMARK 返回首页。
切换到 Workstation。pwd 打印工作目录,ls 列出文件,cat 打印文本文件。相对路径从当前目录解析。
pwd
预期为 /srv/daymark-sales。
ls
cat orders.csv
这些行与网站一致。python3 运行提供的计算程序,其绝对路径不受当前目录影响。> 将标准输出重定向到文件,并在程序运行前替换该文件。请使用独立候选文件,绝不要直接写已发布报表:
/usr/bin/python3 /opt/labex/d10/summarize.py orders.csv > candidate.csv
cat candidate.csv
item,quantity,value_cents
Cake,2,900
Espresso,6,1800
Tea,3,750
TOTAL,11,3450
源记录使用单价,报表使用合并数量与总价值。退出状态为零表示成功,非零表示失败。$? 展开为上一条命令的状态,因此应紧接目标命令读取。echo 打印文本:
/usr/bin/python3 /opt/labex/d10/summarize.py fixtures/broken.csv > rejected.csv
echo $?
预期显示 Export failed: ... 和 1,这是刻意安排的失败。即使计算失败,cat rejected.csv 也会显示表头:部分输出不是有效报表。现有下载仍包含 TOTAL,1,100,因为你只写了独立候选文件。路径出错时,用 pwd 和 ls 检查,执行 cd /srv/daymark-sales 返回后重试候选命令。cd 用于切换目录。保留源订单和现有报表。
仅在成功后发布
在这一步,你将替换不安全的包装脚本,演示保留旧文件与恢复后的更新。从 Workstation 开始。包装脚本接受两个参数:输入 CSV 与发布路径。先读取它:
cat publish.sh
直接使用 > 可能在失败的计算结束前截断已发布文件。应先在目标旁创建临时文件,成功写完后再重命名。在同一文件系统内,重命名会原子替换可见文件名:读者看到完整旧内容或完整新内容。这不提供数据库事务或磁盘损坏保护。
Bash 运行这个小包装脚本。set -e 在普通命令失败时停止;-u 拒绝未设置变量。$1 与 $2 是两个参数。name=value 为 shell 变量赋值,= 两侧不加空格;$(command) 捕获命令输出。引号保留路径中的空格。mktemp 根据 XXXXXX 后缀创建唯一文件。trap ... EXIT 在成功或失败退出时删除残留临时内容。chmod 644 允许所有者写入,所有人读取。mv -fT 替换精确的目标文件名;-T 遇到目标目录时拒绝,而非把文件移入目录。printf 打印发布详情;id -u 显示实际数字用户,sha256sum 标识文件字节。
cat > publish.sh 只替换你的包装脚本。带引号的 SH 开始 here-document:一直粘贴到独占一行的最后一个 SH。引号使美元符号保持字面形式。
cat > publish.sh <<'SH'
#!/bin/bash
set -eu
input=$1
output=$2
tmp=$(mktemp "${output}.XXXXXX")
trap 'rm -f "$tmp"' EXIT
/usr/bin/python3 /opt/labex/d10/summarize.py "$input" > "$tmp"
chmod 644 "$tmp"
mv -fT "$tmp" "$output"
printf 'Published %s uid=%s cwd=%s sha256=' "$output" "$(id -u)" "$PWD"
sha256sum "$output"
SH
预配置账户 daymark-report 拥有 reports/;它能写报表,而网站只读取报表。sudo -u 以该账户运行。/bin/bash 是绝对路径;显式传给 Bash 时,包装脚本无需可执行权限。
sudo -u daymark-report /bin/bash /srv/daymark-sales/publish.sh orders.csv /srv/daymark-sales/reports/sales.csv
cat reports/sales.csv
预期得到完整的 TOTAL,11,3450 报表。cp 复制文件;只覆盖本练习的 orders.csv,保留原始 fixture。接着故意传入格式错误的输入:
cp fixtures/broken.csv orders.csv
sudo -u daymark-report /bin/bash /srv/daymark-sales/publish.sh orders.csv /srv/daymark-sales/reports/sales.csv
echo $?
预期出现刻意安排的失败及 1。切换到 Daymark,刷新 Reports,重新下载,确认 TOTAL,11,3450 仍然存在。失败尝试既不能发布表头,也不能删除原文件。
返回 Workstation。使用多出两份 Tea 的新有效闭店数据恢复。它必须产生 13 项、3950 分,不能永远保留旧报表:
cp fixtures/next.csv orders.csv
sudo -u daymark-report /bin/bash /srv/daymark-sales/publish.sh orders.csv /srv/daymark-sales/reports/sales.csv
cat reports/sales.csv
预期包含 Tea,5,1250 与 TOTAL,13,3950。在 Daymark 中刷新 Records 和 Reports,再次下载;源数据与报表现在一致。权限被拒绝时,检查精确输出路径并重试 sudo -u daymark-report 命令,不要开放所有人写权限。包装脚本有拼写错误时,用完整 here-document 替换并重试。临时文件建在输出旁,使最终重命名保持在同一文件系统内。
总结
你练习了完整 CSV 发布、有意义的失败状态与恢复。计划报表只有在实际执行身份、触发记录与业务总数一致时才有用。本单机练习使用合成数据与短周期演练,不是完整的生产报表平台。


