19

I kept getting shell scripts that looked fine and then nuked a directory because an unset variable expanded to empty and rm -rf "$DIR/" became rm -rf /. Adding set -e alone is a trap: it silently doesn't trigger inside pipelines or command substitutions.

What finally worked was making the prompt spell out the exact strict-mode preamble AND ban the patterns that defeat it, then require a trap for cleanup. I also make it quote every expansion and prefer [[ over [.

Does anyone have a good way to get it to handle the set -e + grep returns 1 gotcha without wrapping every line in || true?

THE PROMPT
Write a POSIX-friendly Bash 4+ script `{SCRIPT_NAME}.sh` that {GOAL}. Target Linux and macOS.

Safety contract (do all of these, do not skip any):
- Start with `#!/usr/bin/env bash` then `set -Eeuo pipefail` and `IFS=$'\n\t'`.
- Quote EVERY variable expansion: `"$var"`, `"${arr[@]}"`. Never leave an unquoted `$x`.
- Never `rm -rf` a path built from a variable without first asserting the variable is non-empty and not `/`.
- Use `[[ ... ]]` for tests, `$(...)` not backticks, and `local` for all function vars.
- Add `trap cleanup EXIT INT TERM` that removes any temp files created with `mktemp`.
- Parse flags with a `while getopts` or a `case` loop; provide `-h/--help` usage and exit 2 on bad usage.
- Preflight: check every required command exists (`command -v`), fail with a clear message if not.
- Print errors to stderr via a `die()` helper that logs and `exit 1`.

Explain in comments how you handle the `set -e` vs. `grep` (exit 1 = no match) gotcha for any command whose non-zero exit is expected. End with a short REVIEW listing each dangerous operation in the script and why it is now safe.
1IFS=$'\n\t' is underrated. Half of bash's word-splitting horror stories disappear once you drop the space from IFS.regex_rob 2 months ago
3The empty-and-not-slash assertion on rm targets has saved a coworker's laptop, no joke. Bake it into the prompt every time.sudo_sam 2 months ago
add a comment

1 Answer

18

For the grep gotcha I stopped fighting set -e and started being explicit about intent. Two patterns cover 95% of cases: if grep -q pat file; then ... when you branch on it, and count=$(grep -c pat file || true) when you genuinely want the number and zero matches is fine. Tell the model to pick based on whether the result is a boolean or a value, and it stops sprinkling || true everywhere.

THE PROMPT
Add rule: for any command whose non-zero exit is a valid outcome (grep no-match, diff difference), either test it directly in `if`, or capture with `|| true` ONLY when a false/zero result is semantically correct. Do not add `|| true` to commands whose failure is a real error.

Your Answer