<!-- skill: Bash Strict | version: 1.0.0 | description: set -euo pipefail, traps, getopts CLIs, logging and shellcheck | category: System & Productivity -->
## Instructions
- Start every script with `#!/usr/bin/env bash` + `set -euo pipefail` + `IFS=$'\n\t'`; add `-x` via `DEBUG=1` flag, never hardcoded in production.
- Quote everything: `"$var"`, `"$(cmd)"`, `"${arr[@]}"`; use `[[ ]]` not `[ ]`; `(( n++ ))` is unsafe under `set -e` — use `(( ++n ))` or `n=$((n+1))`.
- Traps/cleanup: `trap cleanup EXIT INT TERM`; cleanup removes `$TMPDIR` workdir, kills child PIDs, restores `set +x` noise. Create tmp with `tmp=$(mktemp -d)` and `rm -rf "$tmp"` in trap.
- Fail fast with messages: `die() { echo "ERROR: $*" >&2; exit 1; }`, `need() { command -v "$1" >/dev/null || die "missing $1"; }`; check `$#` args before doing work.
- getopts CLI pattern: short flags `-hvn:` + long via manual shift loop if needed; `-h` prints usage and exits 0; `-v` verbose, `-n` dry-run (echo commands, run nothing destructive).
- Logging: `log()`, `warn()`, `debug()` with timestamps (`printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"`); tee to file with `exec > >(tee -a "$LOG") 2>&1` only after arg parse.
- Pipes: `pipefail` catches mid-pipe failures; `mapfile -t lines < file` over `for x in $(cat)`; process substitution `< <(cmd)` keeps while-loop vars in scope.
- Paths: resolve script dir `SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"`; never rely on cwd; use absolute paths in cron/systemd.
- Shellcheck-clean habits: SC2086 (quote), SC2046 (no unquoted $()), SC2002 (no `cat | grep`), SC2155 (declare then assign), run `shellcheck -S warning script.sh` in CI/pre-commit.
- Idempotence: `mkdir -p`, `ln -sfn`, `install -m 644`, check-before-create; `--force` only with explicit confirm prompt unless `--yes`.
- Secrets: never hardcode; read from env or file (`"${TOKEN:?missing TOKEN}"`); `set +x` around secret echo; `umask 077` for key material.
- Exit codes: 0 ok, 1 generic, 2 usage error, 3+ specific (3 missing dep, 4 network); document in `--help`.
## Examples
- Guard: `[[ -f "$cfg" ]] || die "no config at $cfg"`; require root: `[[ $EUID -eq 0 ]] || die "run as root"`.
- Safe loop: `while IFS= read -r line; do echo "$line"; done < file.txt`.
- Dry-run: `run() { if [[ $DRY -eq 1 ]]; then echo "+ $*"; else "$@"; fi; }` then `run rm -rf "$tmp"`.
- Trap: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT`.
## Workflows
1. Scaffold: shebang + strict + usage + getopts + main function.
2. Validate: `bash -n` syntax → `shellcheck` → test `-n` dry-run → test real on copy.
3. Harden: add traps, logging, need-checks, `--yes` gate for destructive ops.
4. Ship: `chmod +x`, symlink into `~/bin`, add `--help` + README line.
5. Debug: `DEBUG=1 ./script -v` (`set -x`), `PS4='+ ${BASH_SOURCE}:${LINENO} '` for line numbers.
## Templates
```bash
#!/usr/bin/env bash
set -euo pipefail; IFS=$'\n\t'
PROG="$(basename "$0")"; VERBOSE=0; DRY=0
log(){ printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; }
die(){ echo "$PROG: ERROR: $*" >&2; exit 1; }
usage(){ cat <<EOF
$PROG [-h] [-v] [-n] [-o out] <input>
 -h help  -v verbose  -n dry-run  -o output dir
EOF
}
need(){ command -v "$1" >/dev/null || die "missing dependency: $1"; }
OUT="out"
while getopts ":hvn:o:" opt; do case "$opt" in
 h) usage; exit 0;; v) VERBOSE=1;;
 n) DRY=1;; o) OUT="$OPTARG";;
 :) die "-$OPTARG needs a value";; \?) die "unknown -$OPTARG (see -h)";;
esac; done; shift $((OPTIND-1))
[[ $# -eq 1 ]] || { usage >&2; exit 2; }
run(){ if [[ $DRY -eq 1 ]]; then echo "+ $*"; else "$@"; fi; }
main(){ need rsync; mkdir -p "$OUT"; log "input=$1 out=$OUT"; run rsync -a "$1" "$OUT/"; }
main "$@"
```
```bash
# trap + tmp pattern (paste into any script)
TMPD="$(mktemp -d)"
cleanup(){ rc=$?; rm -rf "$TMPD"; exit "$rc"; }
trap cleanup EXIT INT TERM
# secrets: : "${API_TOKEN:?export API_TOKEN first}"
# shellcheck gate: shellcheck -S warning "$0" || exit 1
```
- Cron-safe header: full paths (`/usr/bin/rsync`), `export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, log append `>>/var/log/prog.log 2>&1`.
## Tools
- bash 5.x, shellcheck, shfmt (`shfmt -w -i 2 script.sh`); SARAH shell_run to lint-test.
- `bash -n` syntax check, `PS4` tracing, `bats` for tests if script grows past 150 lines.
## Advanced
- Long flags without getopt: `for a in "$@"; do case "$a" in --yes) YES=1;; --out=*) OUT="${a#*=}";; --) shift; break;; *) die "unknown $a";; esac; shift; done`.
- Locking: `exec 9>"$LOCK" && flock -n 9 || die "already running"` prevents cron overlap.
- Retries: `for i in 1 2 3; do curl -fsSL "$URL" && break || sleep $((i*5)); done`.
- Troubleshooting: `set -e` exits early → add `|| true` on allowed-fail lines; `pipefail` + `grep -q` in `if` is fine (condition context); `unbound variable` → default `${VAR:-default}`; subshell trap loss → set trap in each subshell explicitly.
## Documentation
- `man bash`, `help trap`, `help getopts`.
- https://www.shellcheck.net/ wiki per-code explanations.
- https://mywiki.wooledge.org/BashGuide
