跳转到主要内容

使用 bea 自动化记账

使用 bea 脚本化你的 Beancount 账本:显式解析账本,使用 jq 解析 JSON 信封,根据退出码分支,无人值守运行,并安排夜间检查。

脚本通过四个决策驱动 bea:它读取哪个账本、为机器可读输出使用 --json、使用 jq 获取所需值,以及它依赖的退出码。本指南将逐步讲解这四个决策,然后安排它们。

你需要在运行任务的机器上安装 bea,并且有一个它可以访问的账本。如果你正在开始新账本,请先阅读 CLI 快速入门。关于标志、信封键和退出码的所有事实都在 Beancount CLI 参考 中查找,此处不再重复。

显式选择账本

指定文件名。本地命令从 --file、然后是 $BEA_FILE、最后是工作目录中的 ./main.bean 解析其目标,而计划任务很少在你认为的位置运行。

bea --file ~/books/main.bean check
BEA_FILE=~/books/main.bean bea check
cd ~/books && bea check

全局选项放在命令之前,如 bea --file main.bean check。如果解析的文件不存在,命令以 2 退出并列出所有三个来源,因此 cron 条目中的拼写错误会大声失败,而不是验证错误的账本。通过 --ledger 标志进行托管目标定位尚不存在;bea 永远不会隐式上传本地文件。

读取 JSON 信封

添加全局的 --json 后,每个受支持的命令都会用相同的信封响应:beatargetdatatruncated 和有界列表上的 limit。金额是十进制字符串,日期是 ISO YYYY-MM-DD,因此该值进行比较是安全的,浮点数永远不会进入管道。信封的键在 JSON 和退出码参考 中列出。

bea --json --file main.bean report income-statement | jq .data.net_profit
bea --json --file main.bean list transaction --limit 2 | jq '.data[0].postings[0].units'
bea --json --file main.bean import statement.csv --csv date=Date,amount=Amount,payee=Payee \
  --account Assets:Checking --apply --duplicates skip | jq '.data | {written, ready, duplicates}'

这三个命令 — reportlistimport — 保持其结果形状,因此针对它们编写的 jq 路径仍然有效。bea --json checkbea --json query 今天也发出信封,但它们是交给原生 Beancount 可执行文件的命令,因此脚本应依据 check 的退出状态而不是其输出形状。有意识地选择你的重复策略:当导入需要决策时,仍然需要 --duplicates,如 导入演练 所解释的。

在正确的退出码上停止

依据状态分支,并在重试任何写入操作之前读取错误对象。在 --json 模式下,失败时 stdout 上没有任何输出,stderr 上只有一个对象,其 error.category 命名了类别:validation(1)、usage(2)、auth(3)、conflict(4)。

#!/usr/bin/env bash
set -euo pipefail
 
out=$(mktemp)
err=$(mktemp)
status=0
 
bea --json --file main.bean report income-statement >"$out" 2>"$err" || status=$?
 
case "$status" in
  0) jq -r '.data.net_profit | to_entries[] | "net profit: \(.value) \(.key)"' "$out" ;;
  4) echo "conflict — inspect the ledger before retrying" >&2
     jq -r '.error.message' "$err" >&2
     exit 4 ;;
  *) jq -r '.error | "\(.category) (exit \(.exit_code)): \(.message)"' "$err" >&2
     exit "$status" ;;
esac

退出 4 是脚本绝不能盲目重试的:它意味着结果是冲突或未知,例如外部编辑在写入过程中到达,或 init 目标已存在。检查账本,然后从新的读取开始重试。退出 1 涵盖验证失败和任何其他运行时错误;error.details 携带各个账本错误,error.result 携带部分写入实际做了什么。非零退出绝不保证没有更改。

无需终端运行

bea 自动停止提示。当 stdin 不是终端、设置了 --jsonCI 为真(1trueyeson)时,隐式表示 --no-input。在该模式下,缺少确认会以退出 2 失败,而不是永远等待。

CI=true BEA_NO_UPDATE_NOTIFIER=1 bea --json --file main.bean report balance-sheet
bea --json --file main.bean list transaction --limit 100 --sort oldest

读取在终端中是宽松的,在其他地方是严格的。当账本有加载器错误时,querylistreport--json 下、管道 stdout 下、CI 为真下或使用 --strict 时退出 1;改用命令自己的 --allow-errors 接受部分答案,这也会在 JSON 中设置 ledger_valid: false 并填充 ledger_errors--strict 是镜像:即使在终端中它也拒绝部分答案,当人手动运行同一脚本时这正是你想要的。bea check 没有 --allow-errors — 报告错误是它的全部工作 — 并且当它发现任何错误时总是退出 1。设置 BEA_NO_UPDATE_NOTIFIER=1 以静默被动的更新通知;真值的 CI 已经做到了。

安排检查

每晚运行验证,让退出码成为警报。下面的两个块都是模板 — 路径、计划和时间表由你决定。

# crontab -e — 07:15 daily; cron mails you only when bea exits nonzero
15 7 * * * BEA_NO_UPDATE_NOTIFIER=1 /opt/homebrew/bin/bea --file /home/alice/books/main.bean check
name: ledger
on:
  schedule:
    - cron: "15 7 * * *"
  push:
jobs:
  check:
    runs-on: ubuntu-latest
    env:
      BEA_NO_UPDATE_NOTIFIER: "1"
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - run: uv tool install beancount-io==0.1.0
      - run: bea --file main.bean check
      - run: bea --json --file main.bean report balance-sheet > balance-sheet.json

当任务必须可复现时固定版本,当你更愿意跟踪发布时取消固定。CI 在 GitHub Actions 上已经是真值,因此在你设置任何东西之前提示已关闭,更新通知已静默。这里没有格式化步骤是有意为之。计划任务不应重写它不必重写的文件,因此请在预提交钩子中使用 bea format --check,它不触碰任何文件,并在文件需要格式化时退出 1

在任务中使用托管凭据

从 CI 提供商的密钥存储中设置 BEA_TOKEN,完全跳过浏览器登录。该令牌从环境中读取,从不写入磁盘,因此不会出现在运行者的主目录中供下一个任务找到。

export BEA_TOKEN="$YOUR_CI_SECRET"
bea --json cloud status

退出 0 表示凭据已解析,信封命名了它所属的账户;以 error.categoryauth 的退出 3 表示未解析,消息区分未设置的凭据和被拒绝的凭据。bea cloud logout 对以这种方式提供的令牌不做任何事 — 它既不会撤销它也不会取消设置它,因为另一个任务可能共享它 — 因此请从仪表板撤销泄露的令牌。本地命令根本不需要凭据;只有 bea cloudbea ask 访问托管服务。完整的变量列表在 设置参考 中。

并非所有内容都以 JSON 回答。bea ask 完全拒绝 JSON 模式,bea cloud login 需要人工,而成功的 bea cloud logoutbea cloud ledger clone 不返回 JSON 成功对象 — 请改读它们的退出状态。帮助、版本和 shell 补全输出保持文本形式。

后续步骤

来源:https://beancount.io/zh/docs/Solutions/automate-bookkeeping-with-bea