Bash Dry-Run Mode Pattern Prompt
Add a first-class `--dry-run` mode to a Bash automation script so every mutating action is previewed instead of executed — with a single `run()` wrapper, clear PLAN output, and no accidental side effects.
- Target user
- Engineers hardening destructive Bash scripts before running them in production
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior SRE who refuses to run a mutating script in production until it has a trustworthy `--dry-run` mode. You retrofit dry-run support so cleanly that the same code path runs in both modes. I will provide: - My Bash script (or its list of side-effecting commands: rm, mv, kubectl apply, terraform, aws, systemctl, db writes, etc.) - Which actions are destructive vs. read-only - How the script is invoked (cron, CI, interactive) Your job: 1. **Single chokepoint** — introduce a `run()` wrapper that every mutating command flows through. In dry-run it prints `[DRY-RUN] would run: <cmd>` (properly quoted) and returns 0; in live mode it executes. Read-only commands (`grep`, `kubectl get`, `aws describe`) must NOT be wrapped — dry-run still needs real data to plan correctly. 2. **Classify my commands** — go through my script and tell me which calls to route through `run()`, which to leave direct, and which are ambiguous (e.g. `>` redirections, here-docs, pipelines) and how to handle each. 3. **Quoting** — show how to preview commands safely with arrays (`run rm -rf "$dir"`) and `printf '%q '`, so the printed PLAN is copy-pasteable and never re-expands globs. 4. **Default to safe** — make `--dry-run` the default OR require an explicit `--apply`/`--yes` for live runs; recommend which fits my invocation pattern and explain the tradeoff. 5. **Plan/summary** — at the end, print a count of actions that would run, grouped by type, so a reviewer sees blast radius at a glance. 6. **Pitfalls** — call out: pipelines where only part is mutating, conditionals whose branch depends on a side effect, and `set -e` interactions with `run()` return codes. 7. **Verification** — give me a one-liner to diff dry-run output against the previous run to confirm idempotency. Output: (a) the `run()` wrapper and `DRY_RUN`/`--apply` flag parsing, (b) my script rewritten with calls correctly routed, (c) example dry-run output, (d) a checklist before the first live `--apply`. Bias toward: making dry-run the trustworthy default, never letting a mutation slip past the wrapper.