---
name: set-eou-pipefail
description: >
  Write small, explicit Bash. No set -euo pipefail header. Use when writing or
  editing a shell script, Bash, sh, a one-liner in a repo, a curl|grep pipeline,
  cron, or when the model is about to paste set -e, set -u, set -o pipefail, or
  "strict mode". For the long TLDP gotcha distill use /bash-beginners-gotchas.
  Slash command: /set-eou-pipefail.
---

# Bash do's and don'ts

You are writing a script someone will debug at 3 a.m. Keep it small. Quote it. Check the commands that matter. Do not ask the shell to guess.

## Don't

- Don't put `set -euo pipefail` (or `set -e`, `set -u`, `set -o pipefail`) at the top of a file as a default, template, or "best practice."
- Don't treat `set` as error handling. A `set` option is a switch for one problem, in the smallest scope that solves it, then off.
- Don't use `-e`. It does not mean "exit on failure." It is ignored in `if`/`while`/`until` tests, on the left of `&&`/`||`, after `!`, inside functions called from a conditional, and around `local`/`declare`/`export var=$(cmd)`. BashFAQ 105 exists because this is incoherent.
- Don't use `-u` to catch optional arguments or optional env. It kills `cron` for `DEBUG_FLAGS`, explodes `"${arr[@]}"` on older Bash, and grows a crust of `${var-}` on expansions that should have been required.
- Don't enable `pipefail` for the whole file. `grep` no-match is `1`. `yes | head` looks like failure. `cmd | while read` still loses status in a subshell.
- Don't hide a failed `curl` on the left of `&&` and assume a header will save you.
- Don't write a long Bash program. If you are scared you will miss a check, it is the wrong language.
- Don't invent helpers, wrappers, or "strict mode" libraries. Don't comment what the next line already says.
- Don't add pointless defensiveness. A `set` header, `${var-}` on every expansion, `|| true` on commands whose failure matters, and wrapper functions that exist only to "be safe" usually hide the real control flow. That harms readability more than a missing check does. Be explicit at the call site, or don't.

## Do

- Quote expansions: `"${var}"`, `"${1-}"`.
- Strip prefixes and suffixes in the shell: `"${var#pat}"` `"${var##pat}"` `"${var%pat}"` `"${var%%pat}"`. That is a glob, not a regex. Prefer it over `basename`/`dirname`/`sed` for a single known delimiter.
- Check required arguments at the top, in English, with a usage line a human can read. Optional stays optional on purpose. No `-u`.
- Put the check on the command: `curl -fsS "$url" -o "$tmp" || exit 1`. Allowed failure: `grep -e pattern "$tmp" || true`.
- Make functions return status. The caller checks. That is the interface.
- Split a pipe when you need to know which stage died. If you truly want any-stage failure, `set -o pipefail` around that pipe, then `set +o pipefail`.
- Clean up with `trap`, not `set -e`. `tmp="$(mktemp)" || exit 1` then `trap 'rm -f "${tmp}"' EXIT`.
- Prefer one obvious path. Linear beats clever. POSIX-ish beats bash-only unless you need an array.
- Keep the script short enough to read without scrolling. If it grows, stop and use a real language.

## `set` is allowed only when

- `set -o pipefail` around one pipeline whose contract is "any stage fails => the pipe fails."
- `set -u` only if every expansion in that file is required, including `$1`, `$2`, and you have already rejected optional env.
- `set -e` almost never, and only after auditing every `if`, `&&`, `||`, `|`, `local`, function, and `$(...)`.
