---
trigger: glob
globs: **/*.sh,**/*.bash,bin/*,scripts/*
---

# Bash scripts

- Shebang `#!/usr/bin/env bash` and `set -Eeuo pipefail` at the top. If the script must run under POSIX `sh`, say so in a comment and avoid Bash-only syntax.
- Quote every expansion: `"$var"`, `"$@"`, `"${arr[@]}"`. Use `[[ ... ]]` for tests in Bash, `$(...)` not backticks, and `${var:-default}` for optional values.
- Every script passes `shellcheck` with no warnings (disable a check only with an inline comment explaining why) and is formatted with `shfmt -i 2 -ci` or the repo's settings.
- Put logic in functions with `local` variables and a `main "$@"` at the bottom. Constants in `readonly UPPER_CASE`; locals in `lower_case`.
- Temporary files and dirs via `mktemp`/`mktemp -d`, cleaned with `trap 'rm -rf "$tmp"' EXIT`. Never write to fixed paths in `/tmp`.
- Parse options with `getopts` (or a documented `while/case` loop) and print `usage()` on `-h` and on bad input. Exit codes: 0 success, 1 failure, 2 usage error.
- Errors to stderr: `echo "error: ..." >&2` (or a `die` helper). Use `printf` for anything with variables or escapes.
- Never parse `ls` output; iterate with globs (`for f in ./*.txt`) or `find ... -print0 | xargs -0` / `while IFS= read -r -d ''`. Use `read -r` always.
- Destructive commands (`rm -rf`, `git push --force`, `kubectl delete`) guard against empty variables (`"${dir:?}"`) and support a `--dry-run` flag where practical.
- Check required tools up front: `command -v jq >/dev/null || die "jq is required"`.
- Portability: note GNU vs BSD differences (`sed -i`, `date`, `readlink -f`) when scripts run on macOS and Linux; prefer portable forms.
- No secrets in scripts or `set -x` output; read them from the environment.
- Scripts longer than ~200 lines or needing data structures are a sign to switch to Python/Go; suggest it rather than growing Bash.

## Tests
`bats` tests in `test/` for scripts with logic, run with `bats test/`.
