跳转到主要内容

使用 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 后,每个支持的命令都返回相同的信封:bea、target、data、truncated 以及在有限列表上的 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}'

这三个命令——report、list 和 import——保持其结果格式不变,因此针对它们编写的 jq 路径仍然有效。bea --json check 和 bea --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 不是终端、设置了 --json、或 CI 为真值(1、true、yes 或 on)时,都隐含 --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

读取在终端中是宽松的,在其他地方是严格的。当账本有加载器错误时,query、list 和 report 在 --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.2.0
      - run: bea --file main.bean check
      - run: bea --json --file main.bean report balance-sheet > balance-sheet.json

第一个由引擎支持的命令会下载托管的引擎,因此运行器需要网络访问。要在多个任务之间复用,请缓存 ~/.local/share/bea/engine,并使用包含运行器操作系统、架构、Python 版本和固定 bea 版本的键。当任务需要可复现时固定版本,当你更愿意跟踪版本发布时可以取消固定。在 GitHub Actions 上 CI 已经为真值,因此提示已关闭,更新通知在你设置任何内容之前就已静默。这里故意没有格式化步骤。定时任务不应重写它不必更改的文件,因此在 pre-commit 钩子中使用 bea format main.bean --check,它不会触碰任何文件,并在文件需要格式化时以退出码 1 退出。

在任务中使用托管的凭据​

设置 BEA_TOKEN,来自你的 CI 提供商的密钥存储,完全跳过浏览器登录。令牌从环境变量中读取,永远不会写入磁盘,因此不会在运行器的主目录中留下任何东西供下一个任务找到。

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

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

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

下一步​

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