Claude CodeでHDF5を読ませる — h5pyで構造を渡しスライスで抜く手順
巨大なHDF5ファイルをClaude Codeの文脈に丸ごと入れず、階層・shape・dtypeだけを渡す手順です。Read denyの範囲と、スライスで少量だけ抜く小さなスクリプトの置き方をまとめます。
HDF5ファイルをClaude Codeに読ませるときの要点は、中身を読ませずに地図だけ渡すことです。階層、shape、dtype、属性が分かれば、解析コードを書くには足ります。数百MBの配列そのものを文脈に入れる理由はありません。
この記事では、h5pyで構造だけを出力する小さなスクリプトと、少量だけ抜き出すスライス用スクリプトを用意します。あわせて、Readのdenyルールがどこまで効き、どこから効かないかをまとめます。
なぜHDF5はそのまま読ませないのか
HDF5ファイルは、データセット(配列状のデータ)とグループ(フォルダ状の入れ物)を持つバイナリ形式のコンテナです。h5pyの入門ドキュメントは、これを「グループは辞書のように、データセットはNumPy配列のように扱える」と説明しています。
つまり、見たいのは配列の値ではなく、辞書のキーと配列のshapeであることがほとんどです。
Claude CodeのReadツールは、行番号つきでファイルの中身を返す仕組みです。ツールのリファレンスには、.xlsxや.parquet、.zipのように、バイナリとして扱う拡張子のファイルは拒否するとあります。.h5がその一覧に入るかどうかは、ドキュメントに書かれていません。拒否されるかどうかを当てにせず、denyルールで入口を決めておくほうが確実です。
構造を渡す方針は、次の3つに分かれます。
HDF5を文脈に入れずに扱う流れ
- 1
構造だけを出す
階層とshape、dtype、圧縮、属性だけを一覧にします。値は1つも読みません。
- 2
Readを止める
.h5と.hdf5へのReadをdenyに入れ、ファイルツールでの直読みを塞ぎます。 - 3
必要な部分だけ抜く
スライスで数行だけ取り出すスクリプトを経由させ、出力量に上限をかけます。
構造だけを出すスクリプトを置く
まず、手元で試せるサンプルを作ります。2グループ・3データセットで、合計の約37MBの小さなファイルです。
python -m venv .venv && .venv/bin/pip install h5py numpy
.venv/bin/python make_sample.pymake_sample.pyの中身は次のとおりです。乱数のシードを固定しているので、後で示す数値は再現できます。
import h5py, numpy as np
with h5py.File("sample.h5", "w") as f:
f.attrs["instrument"] = "demo"
g = f.create_group("run01")
d = g.create_dataset(
"signal",
data=np.random.default_rng(0).normal(size=(100000, 64)).astype("float32"),
chunks=(1000, 64), compression="gzip")
d.attrs["unit"] = "V"
g.create_dataset("timestamp", data=np.arange(100000, dtype="float64"))
f.create_group("run02").create_dataset(
"signal", data=np.zeros((50000, 64), dtype="float32"))次が構造の出力用スクリプトです。visititemsは、ファイル全体を再帰的にたどるためのGroupのメソッドで、h5pyの入門に例があります。ここでは値には触れず、shape、dtype、chunks、compression、nbytes、属性だけを読みます。
# tools/h5_outline.py
import sys
import h5py
def show(name, obj):
pad = " " * name.count("/")
if isinstance(obj, h5py.Dataset):
print(f"{pad}{name} shape={obj.shape} dtype={obj.dtype} "
f"chunks={obj.chunks} compression={obj.compression} "
f"nbytes={obj.nbytes}")
else:
print(f"{pad}{name}/ (group)")
for k, v in obj.attrs.items():
print(f"{pad} @{k} = {str(v)[:60]}")
with h5py.File(sys.argv[1], "r") as f:
print("/ attrs:", dict(f.attrs))
f.visititems(show)属性の値をstr(v)[:60]で切っているのは、属性に長い文字列や大きな配列が入っている場合に備えるためです。
手元のh5py 3.16.0でサンプルに対して実行すると、次の出力になりました。
.venv/bin/python tools/h5_outline.py sample.h5/ attrs: {'instrument': 'demo'}
run01/ (group)
run01/signal shape=(100000, 64) dtype=float32 chunks=(1000, 64) compression=gzip nbytes=25600000
@unit = V
run01/timestamp shape=(100000,) dtype=float64 chunks=None compression=None nbytes=800000
run02/ (group)
run02/signal shape=(50000, 64) dtype=float32 chunks=None compression=None nbytes=12800000ファイルは約37MBありますが、出力は数行です。run01/signalは10万行×64列のfloat32で、nbytesは2,560万バイトと分かります。chunksとcompressionの列からは、チャンク化とgzip圧縮の有無も分かります。この見積もりがあれば、Claudeが「全部読み込んで平均を取る」コードを書いてよいかを自分で判断できます。
h5lsで済ませる場合
HDF5のコマンドラインツールに含まれるh5lsでも、構造は取れます。HDF5のドキュメントによると、--recursiveは全グループを再帰的に列挙し、--verboseは出力を詳しくします。--dataはデータセットの値を出力するオプションです。
h5ls --recursive --verbose sample.h5h5lsが入っている環境ならこれで足ります。ただし、--dataを付けると値が標準出力に出て、そのまま文脈に入ります。Claudeにh5lsを使わせるなら、--dataを付けないことをCLAUDE.mdに書いておきます。
.h5をReadのdenyに入れる
次に、.claude/settings.jsonで.h5と.hdf5へのReadを止めます。
{
"permissions": {
"deny": [
"Read(**/*.h5)",
"Read(**/*.hdf5)"
],
"allow": [
"Bash(python tools/h5_outline.py *)",
"Bash(python tools/h5_slice.py *)"
]
}
}ReadとEditのルールはgitignoreのパターン記法で書きます。**/を頭に付けると、サブディレクトリのdata/run3/x.h5にもマッチします。2つ目のallowは、2本のスクリプトの実行だけを確認なしで通すための設定です。*は空白を含む任意の文字列にマッチするので、引数のファイル名が変わっても同じルールで通ります。
Readのdenyは、EditとWriteも同じパスで止めます。.h5を新しく作る操作も止まるため、解析の出力ファイルは別の拡張子か別のディレクトリに置きます。書き込みの扱いは.envを読ませないRead denyの書き方に、Writeの注意点とあわせてまとめています。
denyが効かない経路を知っておく
ここで混同しやすい点があります。Readのdenyが止めるのは、組み込みのファイルツールと、cat・head・tail・sed・teeのようにClaude Codeが認識できるファイル系コマンドです。ドキュメントは、ファイル名を示さずに読むコマンドや、PythonやNodeのスクリプトが自分でファイルを開く経路には適用されないと明記しています。
この記事の方針と、その性質はぴったり噛み合います。
| 経路 | denyの効果 | この手順での扱い |
|---|---|---|
Readツールで.h5を開く | denyの効果止まる | この手順での扱い意図どおり |
cat data.h5のようなファイル系コマンド | denyの効果止まる | この手順での扱い意図どおり |
python tools/h5_outline.py data.h5 | denyの効果止まらない | この手順での扱い構造を取る正規ルート |
python tools/h5_slice.py data.h5 ... | denyの効果止まらない | この手順での扱い値を取る正規ルート(上限つき) |
h5ls --data data.h5 | denyの効果止まる対象に載っていない | この手順での扱い手順書で禁止する |
スクリプト経由は止まらないので、値を取り出す量はスクリプト自身が決めます。denyは「Claudeが直接ファイルを開く入口」を閉じる役、スクリプトの上限は「抜き出し量」を守る役です。
OSレベルで特定のパスへの読み取りを塞ぎたいときは、サンドボックスの機能を使います。設定の書き方はsandbox.filesystem.denyReadの記事にあります。コマンド文字列で止める方式がすり抜けられる例は、拒否ルールをevalですり抜けられる問題で扱っています。
スライスで少量だけ抜くスクリプトを置く
値が必要になる場面は必ず来ます。外れ値の確認、先頭数行のフォーマット確認、特定区間の波形などです。そのときは、h5pyの配列スライス記法で必要な範囲だけを読みます。h5pyのドキュメントによると、スライスはHDF5のhyperslab選択に変換されるため、データセット全体を読み込まずに一部だけ取り出せます。
# tools/h5_slice.py
# 使い方: python tools/h5_slice.py FILE DATASET 0:5 0:4
import sys
import h5py
import numpy as np
MAX_ELEMENTS = 200
def parse(spec):
a, b = (spec.split(":") + [""])[:2]
return slice(int(a) if a else None, int(b) if b else None)
path, name, *specs = sys.argv[1:]
with h5py.File(path, "r") as f:
dset = f[name]
sel = tuple(parse(s) for s in specs) or (Ellipsis,)
part = dset[sel]
if part.size > MAX_ELEMENTS:
sys.exit(f"{part.size} 要素は多すぎます(上限 {MAX_ELEMENTS})。範囲を狭めてください")
np.set_printoptions(precision=3, suppress=True, linewidth=100)
print(part)サンプルの先頭3行×4列を取り出すと、次の出力です。
.venv/bin/python tools/h5_slice.py sample.h5 /run01/signal 0:3 0:4[[ 0.126 -0.132 0.64 0.105]
[ 0.329 -0.259 1.583 1.32 ]
[-0.569 -1.03 -1.043 0.268]]範囲が広すぎる場合は、上限で止まります。100行×4列(400要素)を指定した結果は次のとおりです。終了コードは1でした。
400 要素は多すぎます(上限 200)。範囲を狭めてください上限の判定はスライスを読み込んだ後に行うため、巨大な範囲を指定するとメモリへの展開自体は起きます。止まるのは出力側です。メッセージで「範囲を狭める」と伝えているので、Claudeは次の呼び出しで範囲を絞り直せます。上限はスクリプトの定数なので、扱うデータの性質に合わせて変えられます。
全部読みになるコードを避ける
h5pyではdset[...]やdset[()]、dset[:]が全データの読み込みです。ドキュメントも、()を空のタプルとして渡すと全データを取得すると説明しています。Claudeが解析コードを書くときに、うっかりdset[:]と書くと、メモリに2,560万バイトを展開します。サンプルのrun01/signalはまだ小さいほうで、実データではギガバイト級になり得ます。
チャンク化されたデータセットには、チャンク単位で読むiter_chunks()があります。h5pyのドキュメントは、チャンクの形が読み込み速度に影響し、1要素を読むだけでもチャンク全体が関わると説明しています。chunks=(1000, 64)のサンプルで言えば、1要素の読み取りでも1,000行ぶんを展開します。解析の指示に「チャンク単位で処理する」と書いておくと、全展開を避けたコードが出やすくなります。
落とし穴
スライスを2回重ねても読み込み量は減らない
dset[0:1000][5]のように2回インデックスすると、最初の[0:1000]で1,000行がメモリに読み込まれ、その後でNumPy側が5行目を選びます。h5pyのドキュメントも「インデックスを1回行うとNumPy配列がメモリに読み込まれる」と説明しています。取りたい範囲はdset[5, :]のように1回のスライスにまとめます。
CLAUDE.mdに手順を書いておく
スクリプトと権限を整えたら、使い方をCLAUDE.mdに残します。これで、新しいセッションでも同じ流れで進みます。
## HDF5ファイルの扱い
- `.h5` / `.hdf5` は Read しない(settings.json で deny 済み)
- 構造は `python tools/h5_outline.py <file>` で確認する
- 値が必要なときは `python tools/h5_slice.py <file> <dataset> 0:5 0:4` で
200要素以内だけ取り出す
- `h5ls` に `--data` を付けない
- 解析コードでは `dset[:]` で全体を読まず、チャンク単位かスライスで読む
- 解析結果は `.h5` 以外の拡張子で `out/` に書く指示のしかたは、数字を入れて具体的にするのがコツです。「上限200要素」「dset[:]を使わない」と書いたほうが、「大きいデータは慎重に」より従いやすくなります。
最後に、Claudeへの依頼文の例を挙げます。
data/run3.h5 の run01/signal から、10万行の平均と標準偏差を列ごとに出す
スクリプトを書いて。まず tools/h5_outline.py で構造を確認し、全データは読み込まず
チャンク単位で集計して。値の確認が必要なら h5_slice.py を使って。この依頼なら、Claudeは構造の出力を読み、nbytesから全展開が重いと判断し、chunks=(1000, 64)に合わせたチャンク単位の集計コードを書く流れになります。実際の動きはモデルとセッションによって変わるため、出力された集計コードにdset[:]が混ざっていないかは、レビューで確かめます。
構造だけで足りない場面
構造から分からないことは、次のようなものです。
- 値の分布や欠損の有無。必要なら、スクリプトにクエリ(例: 列ごとの最小・最大・NaN数)を足して要約だけ返します。生の値の代わりに、集計済みの数値を渡す形です
- 複合型(compound)のフィールドの意味。属性にメタデータがあれば
h5_outline.pyが拾います。なければデータ提供元の仕様書が必要です - 外部リンクや壊れたリンク。
h5lsには、シンボリックリンクを辿る--follow-symlinksと、辿れないリンクを検出する--no-dangling-linksがあります
集計用のサブコマンドを足す場合も、返すのは常に要約にします。「出力量を決めるのはスクリプト」という原則を崩さなければ、.h5のdenyと組み合わせた運用が保てます。
データ移行のように、型や件数を突き合わせる別の場面は項目マッピング表の作り方に例があります。権限ルールの確認や追加は、/permissionsコマンドから対話的に行えます。
まとめ
HDF5をClaude Codeに読ませるときは、構造用とスライス用の2本のスクリプトを間に挟み、.h5のReadをdenyに入れます。denyはファイルツールからの直読みを止め、スクリプト経由の読み取りは止めないので、出力量の上限はスクリプトの側に持たせます。
最初の1回は、手元の実データにh5_outline.pyを当てて、nbytesの合計を見るところから始められます。合計がメモリに載らない大きさなら、解析の指示に「チャンク単位」と書く価値が高いデータです。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。