プログラミング
Once: CLIコマンドの出力をキャッシュする
Once: Cache CLI Commands (github.com)
要約
「Once」は、CLIコマンドの実行結果をキャッシュし、一定期間再利用できるツール。特に、1Passwordのような認証を頻繁に要求されるコマンドの実行回数を減らすのに役立つ。コマンド、ディレクトリ、テナントキーが一致すればキャッシュされた出力が返され、バックグラウンドデーモンが管理する。
全文翻訳
Once: CLIコマンドの出力をキャッシュする
once コマンドを一度実行し、その出力をしばらく再利用します。
once は現在のディレクトリでコマンドを実行し、その標準出力を印刷し、小さなユーザーごとのバックグラウンドデーモンにメモリ内に保持します。同じコマンド、ディレクトリ、テナントキーでの繰り返し呼び出しは、エントリが期限切れになるまでメモリから応答されます。デーモンは、最後のエントリが期限切れになると自動的に停止します。
典型的なユースケース:指紋認証を毎回承認せずに1Passwordからシークレットを読み取ること。
# once、例えば ~/.zshrc で:
# このターミナルセッション用のテナントキー
export ONCE_TENANT="${ONCE_TENANT:-$(uuidgen)}"
# 後でスクリプト / direnv / mise.toml で作業中に
export GITHUB_TOKEN="$(once --ttl 8h --no-dir -- op read op://Private/GitHub/token)"
最初の呼び出しで指紋認証が求められますが、同じテナントキーで8時間以内の後続の呼び出しでは求められません。
mise を任意のディレクトリ階層または特定のプロジェクトで使用する場合、mise.local.toml:
[env]
ONCE_TENANT = "5D0F399A-5091-43F1-9C43-7C5A2377D90D" # このコンテキストに固有
TOKEN = "{{ exec(command='once --ttl 12h --no-dir -- op read op://Private/something/token') }}"
インストール
go install github.com/alex0ptr/once@latest # Go 1.27+、標準ライブラリ以外の依存関係なし
macOSおよびLinuxのみ。
使用法
once --help
ここから始めるのが良いでしょう:全てのオプションとコマンドがリストされています(once helpと-hも機能します)。より詳細なドキュメントを読むには、続けてください。
once --ttl DURATION [--until TIME] [--tenant KEY] [--refresh] [--no-dir] -- COMMAND [ARGS...]
once status
once clear
once help
オプションの意味
--ttl 結果をキャッシュする期間。例: 30m, 1h, 24h (必須)。
--until 絶対的な有効期限の上限(下記参照)。
--tenant キャッシュコンテキストを分離するキー(下記参照)。デフォルトは $ONCE_TENANT です。どちらか一方が必須です。
--refresh キャッシュを無視し、コマンドを実行してキャッシュ値を上書きします。
--no-dir キャッシュキーから作業ディレクトリを除外します(下記参照)。
once status はデーモン、そのエントリ数、および停止予定時刻を表示します。
once clear は全てのキャッシュ値を削除し、デーモンを停止します。
-- の後の全ては直接実行されます(シェルなし)。環境変数、作業ディレクトリ、ターミナルはシェルから継承されます。パイプ、グロブ、またはその他のシェル機能が必要な場合は、自分でラップしてください:
once --ttl 1h -- sh -c 'op item get "AWS" --format json | jq -r .fields[0].value'
標準出力のみがキャッシュされます。標準入力と標準エラーはそのまま渡されるため、インタラクティブなプロンプトは機能し続けます。ゼロ以外の終了コードで終了したコマンドの結果はキャッシュされず、終了コードはそのまま渡されます。
--no-dir によるディレクトリ非依存キャッシュ
デフォルトでは、作業ディレクトリはキャッシュキーに含まれます。なぜなら、同じコマンドが異なるディレクトリで異なる出力を生成する可能性があるからです(git rev-parse HEAD, cat .version, …)。出力がディレクトリに依存しないコマンド(op readなど)の場合、--no-dir を渡して、1つのキャッシュされた値が全てのディレクトリで機能するようにします。ミスの場合、コマンドは現在のディレクトリで実行されます。--no-dir を使用して作成されたエントリと使用せずに作成されたエントリは別々です。
--until によるキャッシュの制限
--until は絶対時刻を取ります。エントリは、--ttl または --until のうち早い方の時刻で期限切れになります。once自体は相対日付を計算しません。シェルに計算させてください。受け入れられる形式:2026-10-09T06:00:00+02:00, 2026-10-09T06:00:00+0200, 2026-10-09T06:00, 2026-10-09 (ゾーンなしの形式はローカル時間)。
最大1日キャッシュしますが、今日の午後10時を過ぎることはありません:
once --ttl 24h --until "$(date +%F)T22:00" --no-dir -- op read op://Work/DB/password
明日の朝を過ぎることはありません:
# GNU date (Linux)
once --ttl 24h --until "$(date -d tomorrow +%F)T06:00" -- op read …
# BSD date (macOS)
once --ttl 24h --until "$(date -v+1d +%F)T06:00" -- op read …
--until が既に過去にある場合、コマンドは通常通り実行され、その結果はキャッシュされません。
テナントキー
キャッシュキーは HMAC-SHA256(tenant, working directory ‖ command ‖ args) です(--no-dir が設定されている場合はディレクトリなし)。テナントキーは名前空間であり、セキュリティ境界ではありません。また、秘密にしておくためのものではなく、設定ファイルに存在する可能性があります。それは2つのことを行います:
キャッシュコンテキスト(シェル、プロジェクト、スクリプト)を分離します:異なるテナントキーを持つコンテキストは独自の Сエントリを持ち、シークレットを自分で取得する必要があります。
キャッシュキーを実質的に推測不可能にします。
他のプロセスがキャッシュを使用することを防ぐものではありません。ユーザー自身のプロセスはキャッシュを使用できます。下記セキュリティモデルを参照してください。
上記の ${ONCE_TENANT:-$(uuidgen)} パターンは、新しいターミナルごとに独自のテナントを提供しますが、そこから開始されたサブシェルやスクリプトはキャッシュを共有します。キャッシュをターミナル間で共有するには、固定値を使用してください。(余談:--tenant で渡されたキーはプロセスリストに表示されます。)
仕組み
once はキーを計算し、Unixソケット経由でデーモンに問い合わせます。ヒットした場合、キャッシュされた出力を印刷して終了します。ミスの場合、コマンドを現在のディレクトリで、あなたの環境で実行します。コマンドが成功した場合、once は必要に応じてデーモンを開始し、有効期限とともにその出力を渡します。デーモンはエントリをメモリのみに保持します。最新の有効期限を追跡し、エントリがなくなるとシャットダウンします。ソケットは $XDG_RUNTIME_DIR/once-<uid>/ (または一時ディレクトリ) に、モード0700のディレクトリ内に存在します。そのため、他のユーザーは接続できません。そのディレクトリには daemon.log も含まれます。ONCE_RUNTIME_DIR を設定すると、別の場所を使用できます。クライアントはキャッシュキーを自分で計算するため、テナントキーはデーモンに送信されません。
セキュリティモデル
他のユーザーからの保護は、Unixソケットとファイルパーミッションによって提供されます。ランタイムディレクトリはモード0700、ソケットは0600です。同じユーザーのプロセスは除外されません。それらはソケットと通信でき、テナントキー(環境変数または設定ファイルから)を読み取ることができるため、あなたと同じようにキャッシュを使用できます。これは設計上許容されています。
値はデーモンのメモリ内に暗号化されずに保持されます。それらはドロップされる前に上書きされます(ベストエフォート。メモリはスワッピングに対してロックされていません)。コアダンプは無効になっています(RLIMIT_CORE 0、LinuxではPR_SET_DUMPABLE 0)、そのためキャッシュされた値がコアファイルに保存されることはありません。
制限
64 MiBを超える出力は渡されますが、キャッシュされません。期限切れは各読み取り時にウォールクロックに対してチェックされるため、ラップトップがスリープしていた後でもエントリは正しく期限切れになります。
ライセンス MIT