-
Indent - 1 tab (8 spaces). Do NOT convert tabs to spaces.
-
Scope - Use local for local vars, readonly for globals.
-
Local order - In functions, declare locals assigned from positional arguments first, in positional order. Then leave
one blank line and declare the function's other local variables alphabetically.
-
Conditions - Always use [[ ... ]]. Use &&/|| instead of -a/-o.
-
Quotes - Quote only necessary elements (e.g., "$HOME"/path/to/file).
-
Ops - Use $(...) for capture, = for string equality.
-
Case - No indent for case patterns, 1 tab for body.
case $var
a)
...
;;
esac
-
Alphabetize arrays, dicts, assignments, and functions if order is irrelevant.
-
Script layout - In standalone scripts, group content in this order:
- Prelude
- Helpers
- Commands
- Main
Do not add a heading for the prelude. The prelude contains only the shebang, the standard prelude, global
variables if any, and then core functions such as abort and warn, alphabetized.
Add the other section headings in this exact shape, with 120 hyphens:
Keep functions alphabetized inside each section.
-
Command functions - In scripts that expose top-level commands, name command entrypoint functions with the
command. prefix, such as command.start() or command.doctor().
-
Main section - Put non-command functions used directly by main, such as usage, dispatch, or help, in the
Main section alphabetically. The main function itself is always the last function in the Main section regardless
of alphabetical order.
-
Comments - Code should be self-documenting. If you need a comment to explain WHAT the code does, consider
refactoring to make it clearer. Unacceptable comments:
- Comments that repeat what code does
- Commented-out code (delete it)
- Obvious comments ("increment counter")
- Comments instead of good naming
- Comments about updates to old code (e.g.
# now supports xyz)
-
POSIX sh contexts: If the target interpreter is sh, /bin/sh, POSIX shell, or a remote heredoc that will be
read by sh, do not use the Bash prelude or Bash-only style rules. The Bash prelude below is only for scripts that
actually run under Bash. For POSIX sh, use a small compatible prelude such as:
set -eu
unset CDPATH
Do not add set -o pipefail, set -E, [[ ... ]], arrays, local, source, function name { ...; }, process
substitution, here-strings, brace expansion, mapfile, readarray, $'...' strings, or Bash redirections such as
&>/dev/null. Use POSIX forms instead: [ ... ], case, . file, name() { ...; }, explicit loops, temp files,
here-documents, printf, and >/dev/null 2>&1. Check POSIX-targeted snippets with shellcheck -s sh when practical.
-
Prelude: Place the following block, including the blank lines, at the beginning of the file
#!/usr/bin/env bash
set -Eeuo pipefail
[[ -z ${TRACE:-} ]] || set -x
unset CDPATH
-
Nullglob: When a script uses glob patterns that may match nothing, add shopt -s nullglob to
the prelude after the standard options:
shopt -s nullglob
Without this, an unmatched glob such as *.txt produces the literal string *.txt:
for f in *.txt; do
...
done
-
Error Handling
abort() {
warn "E: $*"
exit 1
}
warn() {
echo -e "$*" >&2
}
-
Main function - Use main() { ... } and call main "$@".
-
Efficiency - Use shell substitution (${0##*/}) over external tools (basename)
-
Silence - If the command offers a quiet option for silent operation, use that option, ie. grep -q; otherwise,
use the &>/dev/null shell redirection.
-
Arrays - Use mapfile for output capture: mapfile -t arr < <(CMD).
-
Temp Files - Use mktemp + trap.
local tempfile
tempfile=$(mktemp) || exit
trap 'err=$? && rm -f "'"$tempfile"'" || exit $err' EXIT HUP INT QUIT TERM
-
Temp Dirs
local tempdir
tempdir=$(mktemp -d) || exit
trap 'err=$? && rm -rf "'"$tempdir"'" || exit $err' EXIT HUP INT QUIT TERM