Claude Codeで一括リネームのスクリプトを書く — dry-runの対応表を見てからmvを許可
Claude Codeに一括リネームのスクリプトを書かせ、dry-runで変更前後のCSVを出させて確認してから実行するまでの手順と、--applyだけを確認に残すpermissions設定を説明します。
Claude Codeで一括リネームのスクリプトを書く — dry-runの対応表を見てからmvを許可
一括リネームは、間違えたときの被害が見えにくい作業です。mv は上書きしても警告を出さず、数百ファイルの名前が一度に変わるため、あとから元の名前を復元する手がかりが残りません。Claude Codeに頼むときは、mv を直接打たせるのではなく、変更前後の対応表をCSVで出すだけのdry-runと、そのCSVのとおりに mv する実行を分けたスクリプトを書かせます。
この記事では、そのスクリプトの形と、dry-runだけを確認なしで流し、--apply だけを毎回確認に残すpermissions設定を作ります。あわせて、この設定が効かなくなる書き方も扱います。
一括リネームは「計画・確認・実行」の3段に割る
Claude Codeに「ファイル名を全部kebab-caseにして」と頼むと、for ループで mv を並べたコマンドが一度に走りがちです。確認の機会は、ループ全体への1回の承認しかありません。承認する側は、どのファイルがどの名前になるのかを、コマンド文字列から読み取ることになります。
そこで、作業を3段に分けます。
一括リネームを3段に分ける
- 1
計画: dry-runで対応表を作る
スクリプトを引数なしで実行すると、
rename-plan.csvに「旧名・新名・状態」を書き出すだけで、ファイルは動かしません。 - 2
確認: 人が対応表を読む
CSVを開き、新名が衝突している行(
conflict)や、意図しない名前になっている行がないかを見ます。直したい点があれば、Claudeにスクリプトの変換規則を直させて、もう一度dry-runを流します。 - 3
実行: --applyだけを確認に残す
--applyを付けたときだけ、CSVのokの行をmv -nで実行します。この呼び出しだけを、permissionsで毎回の確認に残します。
この分け方のよい点は、確認の対象が「コマンドの文字列」から「CSVの中身」に変わることです。承認ダイアログで読めるのは bash scripts/rename.sh --apply までですが、CSVなら全件の対応が表で並びます。
Claudeにスクリプトを書かせる指示
指示には、仕様の核になる3点を入れます。dry-runが既定であること、衝突を検出すること、実行は --apply を明示したときだけであること、の3つです。
docs/ 直下の *.md を、小文字・スペースをハイフンに置き換えた名前に変えたい。
scripts/rename.sh を作って。仕様は次のとおり。
- 引数なしはdry-run。ファイルは動かさず、rename-plan.csv に old,new,status を出す
- 新名が既に存在する行は status を conflict にする
- --apply のときだけ、rename-plan.csv の status が ok の行を mv -n で実行する
- --apply は rename-plan.csv が無ければ失敗する
作ったら、実行はせず、スクリプトの中身を見せて。最後の1行が大事です。スクリプトを書いたその場で動かさせないと、確認の段に入る前に実行されてしまいます。
出来上がるスクリプトは、たとえば次のような形になります(サンプルとして、手元の空のディレクトリで動かした形です)。
#!/usr/bin/env bash
# 使い方: bash scripts/rename.sh 対応表 rename-plan.csv を作るだけ(dry-run)
# bash scripts/rename.sh --apply rename-plan.csv のとおりに mv する
set -euo pipefail
PLAN=rename-plan.csv
if [ "${1:-}" = "--apply" ]; then
[ -f "$PLAN" ] || { echo "先に dry-run で $PLAN を作ってください" >&2; exit 1; }
tail -n +2 "$PLAN" | while IFS=, read -r old new status; do
[ "$status" = "ok" ] || continue
mv -n -- "$old" "$new"
done
exit 0
fi
echo "old,new,status" > "$PLAN"
find docs -maxdepth 1 -name '*.md' | sort | while read -r old; do
base=$(basename "$old" .md)
new="docs/$(echo "$base" | tr 'A-Z ' 'a-z-').md"
[ "$old" = "$new" ] && continue
if [ -e "$new" ]; then status=conflict; else status=ok; fi
echo "$old,$new,$status" >> "$PLAN"
done
cat "$PLAN"mv -n は、移動先に同名のファイルがあれば上書きしないオプションです。dry-runで衝突を検出していても、確認から実行までの間にファイルが増えることがあるため、二重の安全策として残します。
ただし、mv -n は上書きを避けるだけで、飛ばしたことを知らせません。macOS(bash 3.2.57)で試すと、移動先が既にあっても終了コード0で戻り、直後の echo "renamed" は「成功」と表示しました。スクリプトにログを足すときは、mv の後ろに成功の表示を続けず、[ -e "$new" ] で移動先を先に確かめます。
dry-runの出力で見るところ
docs/ に Setup Guide.md、API Notes.md、api-notes.md、Release Plan.md、readme.md を置いて、引数なしで実行すると、次の対応表が出ます。
bash scripts/rename.shold,new,status
docs/API Notes.md,docs/api-notes.md,conflict
docs/Release Plan.md,docs/release-plan.md,ok
docs/Setup Guide.md,docs/setup-guide.md,okreadme.md は変換後も同じ名前なので、対応表に出ません。API Notes.md は変換先の api-notes.md が既にあるため conflict になり、--apply では飛ばされます。この行が見つかるのが、dry-runを挟む価値です。直接 mv を並べていれば、mv が api-notes.md を上書きしていたかもしれません。
CSVを読むときの観点は3つあります。
conflictの行が出ていないか。出ていれば、どちらを残すかを人が決める- 件数が想定と合っているか。100件のつもりが3件なら、
findの条件が狭すぎる - 新名が意図した形になっているか。先頭の数行と末尾の数行を、目で見る
このスクリプトは、ファイル名にカンマが含まれるとCSVの列がずれます。名前にカンマがありうる対象では、区切り文字を変えるよう、Claudeにスクリプトの修正を頼みます。また、macOSの標準設定のように大文字・小文字を区別しないファイルシステムでは、README.md を readme.md にするような大文字小文字だけの変更は、-e の存在判定が「既にある」と答えて conflict になることがあります。この場合は、いったん別名を経由する変換が必要になるため、Claudeに変換規則の追加を頼みます。
permissionsで --applyだけを確認に残す
次は、権限の設定です。考え方は「dry-runの呼び出しは丸ごと許可し、--apply は毎回聞く」です。
.claude/settings.json には、次のように書きます。
{
"permissions": {
"allow": [
"Bash(bash scripts/rename.sh)"
],
"ask": [
"Bash(bash scripts/rename.sh --apply)",
"Bash(mv *)"
]
}
}allowの Bash(bash scripts/rename.sh) には * がありません。* のないBashルールは、完全に同じコマンド1つにだけ一致します。そのため、bash scripts/rename.sh --apply のように引数が付いたコマンドは、このallowには当たりません。
ここで Bash(bash scripts/rename.sh *) と書いてしまうと、--apply もallowに含まれます。ルールはdeny、ask、allowの順に評価され、最初に一致したものが結果を決めます。askとallowの両方に一致するコマンドはaskが勝つので、--apply にaskを足しておけば、広いallowを書いた場合の保険にもなります。
acceptEditsモードとmvの関係
acceptEdits モードは、ファイル編集に加えて、mkdir、touch、mv、cp のような一般的なファイルシステムコマンドも、作業ディレクトリ内のパスに限って自動承認します。このモードでClaudeが mv を直接打つと、確認なしで通ります。
これを止めるのが、askの Bash(mv *) です。permissions.ask に書いたものは、acceptEdits や bypassPermissions のようにほかの理由で承認されるモードでも、確認が出ます。リネームの作業中だけ acceptEdits で回したいときは、mv の直接実行をaskで押さえておくと、スクリプトを経由しない抜け道を塞げます。
この設定が効かない場面
Bashの権限ルールは、Claudeが書いたコマンドの文字列を照合します。スクリプトの中で何が起きるかは見ていません。ここを取り違えると、設定が安全装置として働かなくなります。
| 状況 | 結果 |
|---|---|
bash ./scripts/rename.sh --apply のようにパスの書き方が違う | 結果askにもallowにも当たらず、モードの既定の判断になる |
timeout 30 bash scripts/rename.sh --apply | 結果timeout などの固定のラッパーは取り除いて照合されるため、askに当たる |
/bin/mv -n a b | 結果Bash(mv *) のaskは、別の呼び方には効かない |
bash -c 'mv a b' | 結果同じく、askに当たらない |
スクリプトの中の mv | 結果コマンド文字列に出ないので、ルールは何も見ない |
この中で見落としやすいのは、最後の行と、スクリプトそのものを書き換えられる点です。dry-runを許可しているということは、Claudeが scripts/rename.sh を編集してから、同じ「引数なし」の呼び出しをすれば、許可のまま中身だけ別物が走る、ということです。
対策は2段あります。
- スクリプトの中身を人が読んで承認したら、
Edit(scripts/rename.sh)をdenyに追加して、以降の書き換えを止める。Editのルールは、ファイルを編集する組み込みツール全般に適用される - OSの水準でパスを限定したいときは、サンドボックスを有効にする。ReadとEditのdenyルールは、Pythonやシェルスクリプトのように、自分でファイルを開く別プロセスには効かないため
{
"permissions": {
"deny": [
"Edit(scripts/rename.sh)"
]
}
}別の対策として、PreToolUseフックで、Bashのコマンド全文を自前のロジックで検査する方法もあります。フックでは、--apply のときに rename-plan.csv が新しいかどうかを調べて、古ければ止める、という検査が書けます。
dontAskモードでは--applyが拒否される
確認を出さないモードに dontAsk があります。このモードは、本来なら確認が出る呼び出しをすべて自動で拒否します。askに入れた --apply も、確認ではなく拒否になります。
CIや claude -p の非対話実行でdry-runだけを回し、CSVを成果物として残したい場合には、この性質が向いています。--apply は人が手元で実行するか、確認が出るモードで流します。dry-runのallowルールは dontAsk でも有効なので、計画の段までは自動化できます。
実行後に戻せるようにする
mv -n は上書きを防ぎますが、名前を変えた事実そのものは元に戻りません。対応表があれば、列を入れ替えた逆向きのCSVで戻せます。--apply の直前に、CSVを日付つきで退避しておく運用を、Claudeにスクリプトへ入れさせます。
cp rename-plan.csv "rename-plan.$(date +%y%m%d-%H%M).csv"戻すときは、退避したCSVの old と new を入れ替えて、status が ok だった行を逆向きに mv -n します。Gitで管理しているディレクトリなら、実行前にコミットしておくのが最も簡単です。履歴を残したい場合は、スクリプトの mv を git mv に置き換えるようClaudeに頼みます。その場合は、askのルールに Bash(git mv *) も足します。
古いCSVを実行しない
この手順には、もう1つ落とし穴があります。dry-runのあとにファイルが増減したり、スクリプトを直したりすると、CSVが現状と合わなくなります。直したのにdry-runを流し直さないまま --apply すると、古い対応表のとおりに動きます。
運用の決まりとして、スクリプトを直したら必ずdry-runからやり直す、と決めます。Claudeに頼むときも、「直したらdry-runを流して、CSVの conflict の件数を報告して」と書いておくと、確認の材料が毎回そろいます。
--apply の確認ダイアログを出す前に、CSVの先頭を開いて、作成日時と件数がスクリプトを直した直後のものかを確かめます。
まとめ
一括リネームで守りたいのは、実行前に全件の対応を人が読むことです。dry-runが既定のスクリプトと、完全一致のallow、--apply と mv に付けたask、の3つを組み合わせると、確認の対象はコマンド名ではなくCSVの中身になります。スクリプトを承認したあとはEditをdenyにして、書き換えで抜けられないようにします。
同じ「読むだけは許可し、書き込みだけ確認に残す」考え方は、DVCの dvc push だけを確認に残す設定や、本番へのスキーマ変更を権限で止める手順でも使えます。&& でつないだコマンドがどう判定されるかは、Bashの複合コマンドと権限判定にあります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。