Bash idioms — strict mode, quoting, parameter expansion, arrays, pipefail, trap cleanup, idempotency, heredocs, and POSIX portability. Auto-load when working with .sh, .bash files, or when the user mentions bash, shell, sh, shellcheck, set -e, or pipefail.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Bash idioms — strict mode, quoting, parameter expansion, arrays, pipefail, trap cleanup, idempotency, heredocs, and POSIX portability. Auto-load when working with .sh, .bash files, or when the user mentions bash, shell, sh, shellcheck, set -e, or pipefail.
Bash
Strict mode
set -euo pipefail
IFS=$'\n\t'
-e is suppressed in conditional contexts (||, &&, if, !); explicit subshells (...)do inherit it — use || true to absorb expected failures.
-u treats unset variables as errors; unset arrays trigger it: declare before use (arr=()) or guard with ${arr[@]+"${arr[@]}"} for optional arrays.
IFS=$'\n\t' prevents accidental word-splitting on spaces in for loops and command substitution.
Quoting and tests
Always "$var" — bare $var triggers word splitting and glob expansion.
'literal' for fixed strings with no expansion needed.
Prefer [[ ]] over [ ]: supports =~ regex, no word splitting, lexical string comparison.
$() over backticks: nestable, readable, no escaping required.
if [[ "$filename" =~ \.(sh|bash)$ ]]; then
shellcheck "$filename"fi
Parameter expansion
${var:-default} — substitute default if unset or empty.
${var:?error msg} — abort with message if unset; pairs well with set -u.
${var%suffix} — strip shortest suffix match (e.g. strip extension).
${var//pattern/repl} — replace all occurrences in-place.
Arrays and word splitting
files=(src/a.sh "src/b script.sh" src/c.sh)
for f in"${files[@]}"; do# each element quoted separately
process "$f"done
"${arr[@]}" — each element as a separate quoted word; always use for iteration.
"${arr[*]}" — all elements joined by IFS[0]; use only for joining to a string.
Never for x in $(cmd) — use mapfile -t arr < <(cmd) or while IFS= read -r line.
Pipelines and subshells
pipefail makes a pipeline fail when any stage fails, not just the last.
Command substitution $(...) runs in a subshell; variable assignments inside don't leak out.
cmd || true absorbs an expected non-zero exit under -e; cmd 2>/dev/null suppresses stderr noise separately.
Background jobs: proc &; always wait "$pid" before consuming results.
Redirect ordering matters: cmd >/dev/null 2>&1 silences all; cmd 2>&1 >/dev/null silences stdout only (stderr still shows — order determines what 2>&1 copies).
Cleanup with trap
tmp=$(mktemp)
trap'rm -f "$tmp"' EXIT
trap'echo "interrupted" >&2; exit 130' INT TERM
Register trap early — after state the handler depends on exists, before risky operations.
trap '...' ERR fires only on non-zero exit codes; use for diagnostic logging (cannot prevent -e from exiting).
Signal names: EXIT (always), ERR (errors), INT (Ctrl-C), TERM (kill).
Alternative: idempotent scripts that are safe to re-run don't need cleanup traps — see §Idempotency.
Idempotency and resumability
COMMITTED=0
[[ -f .committed ]] && COMMITTED=1
if (( COMMITTED == 0 )); then
git commit -m "$msg"touch .committed
fi
Check-before-act: [[ -f sentinel ]] || create_it.
Detect external state via read-only queries: git ls-remote, gh pr view --json state.
Mock external commands by prepending a temp dir containing stub scripts to PATH.
eval/cwd trap: when testing eval "$(script 2>&1)" patterns, capture the script output FIRST from a valid cwd, THEN cd to the eval directory, THEN eval. $(...) launches a subshell that inherits the cwd at expansion time — cd eval_cwd && eval "$(script)" means the script runs FROM eval_cwd, not the original directory, and may exit early.
# Wrong — script inherits eval_cwd as cwd, may exit early if it's not a git repo:cd"$eval_cwd" && eval"$(bash script.sh arg 2>&1)"# Correct — capture first, then move, then eval:
output="$(bash script.sh arg 2>&1)"; cd"$eval_cwd"; eval"$output"
Under set -e, use output=$(…) || handle_error — $? is unreachable because the parent script aborts at the failed assignment before the next line executes. The || forms a conditional context that suppresses set -e and runs the handler on non-zero exit.
Avoid
Backtick substitution `cmd` — use $(cmd).
Unquoted $var and $@ — always quote.
for f in $(ls) or for f in $(find ...) — use globs or mapfile.
Parsing ls output for filenames — use find or shell globs.
eval on user-controlled or external input — command injection risk.
cd dir && cmd without a subshell — if cmd fails, subsequent code runs from the wrong directory; use (cd dir && cmd) to scope the change.
cat file | grep (UUOC) — use grep pattern file.
set -x in production — use PS4 with a debug flag and enable only in targeted blocks.