[H1][CODING-BASH]
Dictum: Functional patterns and strict mode produce maintainable shell automation.
All code follows five governing principles:
- Functional — immutable locals, pure functions, dispatch tables, and tightly bounded mutable shell state
- Polymorphic — one parser, one dispatcher, one logger; extend via table entries not code branches
- Production-hardened — ERR traps, atomic I/O, signal forwarding, cleanup registries, version gating
- Fork-minimal —
printf -v,$(<file),EPOCHSECONDS, fork-free${ }substitution (5.3),BASH_MONOSECONDSmonotonic timing,mapfileover subshell patterns - Ecosystem-first —
rg/fd/jq/sd/choose/mlrover sed/grep/find/cut when available - Executable doctrine — examples and templates must pass syntax, ShellCheck, and their own self-tests
Paradigm
- Immutability:
local -rfor all non-mutating function locals,readonlyfor module-level constants. Mutable state only for argument parsing — frozen viareadonlyin_mainbefore core logic - Dispatch tables:
declare -Arfor O(1) command routing, two-dimensionalverb:resourcekeyed dispatch, option metadata, validation rules, log-level gating, env contract validation (regex patterns per env var).case/esacreserved exclusively for glob/regex pattern matching — never conditional routing - Pure functions: Input via positional parameters, output via stdout or nameref (
local -n). No global reads exceptreadonlyconstants. Side effects isolated to_main, trap handlers, cleanup registries, and explicitly marked shell boundary loops - Metadata-driven help:
_OPT_METAwithshort|long|desc|VALUE_NAME|defaultentries generates_usageprogrammatically. One table entry + onecasebranch per option - Middleware composition:
_use()registers middleware functions into_MIDDLEWAREarray;_run_with_middleware()executes the chain before handler dispatch. Argument parsing in 3 composable phases — subcommand dispatch (O(1) table lookup), flag parsing (case/esac), positional collection - Expression over statement:
${var:-default}over if-empty checks,${var:?message}over assert-not-empty,(( expr ))overtest, parameter expansion over external commands - Fork elimination:
printf -v var '%(%F %T)T' -1over$(date),$(<file)over$(cat file),EPOCHSECONDS/EPOCHREALTIMEover$(date +%s),mapfileoverwhile readloops,BASH_REMATCHovergrep -oP - Atomic I/O: All file writes via
mktemp+ write +mv(rename is atomic on same filesystem).umask 077beforemktempfor sensitive data. Dynamic FDs viaexec {fd}>file
Conventions
Ecosystem tool selection — prefer modern alternatives when available:
| [TASK] | [PREFERRED] | [FALLBACK] | [NEVER] |
|---|---|---|---|
| File search | fd |
find |
ls -R |
| Content search | rg |
grep -rn |
find -exec grep |
| JSON | jq |
python3 -c |
sed/awk on JSON |
| YAML | yq eval |
python3 -c |
sed on YAML |
| CSV/TSV | mlr |
awk -F |
cut for multi-field |
| Stream edit | sd |
sed |
awk for simple sub |
| Column select | choose |
awk '{print $N}' |
cut -d |
| Interactive JSON | jnv |
jq |
-- |
Selection rules:
- Probe availability via
command -vbefore use; fall back gracefully rg/fdintegrate with.gitignoreby default — prefer for repo-aware searchesjqis mandatory for JSON — never parse JSON with sed/awk/grepmlrhandles format conversion (CSV to JSON, TSV to JSON) natively- Pipeline preference: single
awkprogram over chainedgrep | sed | cut
Contracts
Variable discipline
local -rfor all non-mutating function locals.readonlyfor all module-level constants.- Mutable state (parsed args, log level) declared at module level, frozen via
readonlyin_mainbefore core logic. declare -Arfor all dispatch tables, option metadata, and lookup maps.local -n(nameref) for passing arrays to functions — neverevalor indirect expansion.- Nameref return channels: scalar-returning functions take result var as last arg (
_ext "$item" key) and write viaprintf -v "$2"orlocal -n— callers pass a name, never$(). Multi-return via multiple namerefs (e.g.,_project_meta "$slug" name created). - Naming:
UPPER_SNAKEfor constants/env,lower_snakefor locals/functions,_prefix for internal functions.
Control flow
case/esacfor pattern matching (globs, regexes) only — never for if/elif-style routing.declare -Ardispatch tables for command routing:"${_DISPATCH[${cmd}]}" "${args[@]}". Nest for subdomains:_CONFIG_SUBCMDS,_INIT_SUBCMDS.[[ ]]over[ ].(( ))for arithmetic.&&/||for short-circuit.mapfile -t/readarray -d ''overwhile readloops for collection. Streaming consumers may usewhile IFS= read -rwith a comment naming the stream boundary.- Ternary via arithmetic:
(( condition )) && action1 || action2or${var:+if_set}${var:-if_unset}. - Bounded concurrency:
wait -n -p finished_pidwith job-count gate(( ${#jobs[@]} >= MAX_JOBS ))— see_run_poolpattern in examples. - Shell reality exceptions must be explicit: option parsing uses
case; cleanup stacks may use a staticevaltemplate over shell-quoted commands; bounded counters and polling loops may mutate when the mutation is the resource protocol.
Error handling
set -Eeuo pipefail+shopt -s inherit_errexitin every script. No exceptions.- ERR trap with
BASH_COMMAND,BASH_LINENO,FUNCNAMEcontext. Stack trace for multi-level call chains. _CLEANUP_STACKLIFO registry invoked by EXIT trap._CLEANINGguard prevents re-entrant execution on cascading signals.- Exit codes: 0=success, 1=general error, 2=usage error. Custom codes in
EX_*constants. _die()for fatal errors (log + exit)._die_usage()for argument errors (log + hint + exit 2).- Timing via
EPOCHREALTIMEmicrosecond arithmetic:_bench()computes(end_s - start_s) * 1000000 + 10#end_us - 10#start_us— zero forks.
Logging architecture
declare -Ar _LOG_EMIT=([json]=_log_json [text]=_log_text_emit)— format resolved once at startup viareadonly _LOG_EMITTER="${_LOG_EMIT[${LOG_FORMAT:-text}]}"._log()gates on_LOG_LEVELSnumeric threshold, then dispatches via"${_LOG_EMITTER}"— zero branching per call.- JSON emitter:
jq -nc --argfor injection-safe serialization withEPOCHREALTIMEmicrosecond timestamps and optional W3C trace context fields. FUNCNAMEoffset accounts for_info->_log->_LOG_EMITTERcall chain depth (typicallyFUNCNAME[3],BASH_LINENO[2]).
Surface
_prefix for all internal functions. Public surface =_mainentry point only.- One dispatch table per concern — extend by adding entries, not code branches.
- No utility/helper files — colocate all logic in the script.
sourceonly for test frameworks. --self-testflag runs embedded smoke tests and exits — validates dispatch tables, config parsing, and key pure functions.- ~350 LOC scrutiny threshold — investigate compression via dispatch tables and awk programs, not file splitting.
Resources
- Temporary files:
mktemp+_register_cleanup "rm -f -- $(printf '%q' "${tmp}")"or equivalent static quoted cleanup template. Work directories:mktemp -dwithSRANDOMin path for uniqueness. - Signal forwarding for PID 1: trap TERM/INT,
kill -"${sig}" "${_CHILD_PID}", exit with signal code (143/130). Guard on(( _CHILD_PID > 0 )). On 5.3,BASH_TRAPSIGenables unified signal handler with dispatch-table routing by signal number.GLOBSORTcontrols glob ordering (e.g.,-mtimefor newest-first file discovery). - Retry:
_retry_exec max delay max_delay cmd...— exponential backoffdelay=$(( delay * 2 > max_delay ? max_delay : delay * 2 ))withSRANDOMjitter. - Env contracts:
declare -Ar _ENV_CONTRACT=([VAR]='^regex$')validated at startup — dispatch table over env vars, regex per key. - Health endpoint:
socat TCP-LISTEN:${port},reuseaddr,fork SYSTEM:"printf 'HTTP/1.1 200 OK\r\n...'"backgrounded with cleanup registration. - W3C tracing: parse
TRACEPARENTviaBASH_REMATCH, generate viaprintf -v TRACE_ID '%08x%08x%08x%08x' "${SRANDOM}"..., export for child propagation.
Load sequence
Foundation (always):
| [REFERENCE] | [FOCUS] |
|---|---|
| bash-scripting-guide.md | Primitives, strict mode, expansion, arrays |
Task-routed references (load only when the task matches):
| [REFERENCE] | [FOCUS] |
|---|---|
| version-features.md | 5.2/5.3 features, fork-free substitution, version gating |
| variable-features.md | Call stacks, namerefs, traps, process lifecycle, 5.3 vars |
| array-operations.md | Set algebra, structural transforms, higher-order traversal |
| string-operations.md | Transform pipelines, regex extraction, codecs, templates |
| file-operations.md | Atomic writes, FD multiplexing, directory traversal |
| script-patterns.md | Arg parsing, help, ERR traps, parallel, retry |
| bash-logging.md | Structured logging, CI integration, tracing |
| bash-testing.md | bats-core 1.13+ suites, coverage, hypothesis PBT |
| bash-portability.md | Cross-shell compat, containers, POSIX |
| text-processing-guide.md | rg/awk/sd/jq/yq/mlr tool selection |
| validation.md | ShellCheck codes, static analysis, CI |
Examples (read one matching your target archetype before writing):
| [EXAMPLE] | [ARCHETYPE] |
|---|---|
| cli-tool.sh | Two-dimensional verb:resource dispatch CLI |
| data-pipeline.sh | File processing with jq pipelines, accumulation |
| service-wrapper.sh | Container entrypoint, signal dispatch, coproc |
Anti-Patterns
State violations
- MUTABLE STATE:
let/global mutation outsidedeclare -gconfig loading. Uselocal -r/readonly; freeze parsed args in_main. - FORK IN HOT PATH:
$(date),$(cat file),$(wc -l < file)in loops. Useprintf -v,$(<file),EPOCHSECONDS,mapfile.
Control-flow violations
- IMPERATIVE DISPATCH:
if/elif/elsechain for command routing. Usedeclare -Ardispatch table + O(1) lookup. - WHILE-READ COLLECTION:
while IFS= read -r lineloop to build arrays. Usemapfile -t arr < <(cmd). - UNMARKED STREAM LOOP:
while readwithout a streaming-boundary comment. Streaming consumers are valid; collection loops are not. - NAKED WRITE: Direct
>or>>for output files. Usemktemp+mvatomic pattern.
Safety violations
- HARDCODED FD:
exec 3>filewith literal FD numbers. Useexec {fd}>filefor safe dynamic allocation. - UNQUOTED EXPANSION:
$varwithout quotes. Always"${var}"— exceptions only in(( ))arithmetic. - EVAL INJECTION:
eval "$user_string"with untrusted input. Only static cleanup/capture templates over shell-quoted values are allowed; otherwise usedeclare -Ardispatch orcase/esacpattern match. - ECHO OVER PRINTF:
echo -e/echo -nfor formatted output. Useprintf— portable, no ambiguity, format strings.
Organization violations
- UTILITY EXTRACTION:
lib/utils.sh,common.shhelper files. Colocate all logic in the script. - RANDOM OVER SRANDOM:
$RANDOMfor security-relevant randomness (temp names, jitter, tokens). Use$SRANDOM(cryptographic entropy).
Validation gate
- Required:
bash -n script.sh(syntax check), ShellCheck 0.11.0+ clean (static analysis). - Required for executable examples: run
--self-testwhen present. - Reject completion when strict mode, readonly discipline, ShellCheck compliance, or example self-tests are not satisfied.
Skill eval prompts
- Explicit invocation: "Using coding-bash, refactor this .sh CLI into dispatch-table Bash 5.3 style with self-tests."
- Implicit invocation: "Review this deployment script for ShellCheck, strict mode, cleanup, and streaming-loop issues."
- Noisy context: "Ignore CI chatter and only audit the Bash entrypoint."
- Negative control: "Only write PostgreSQL DDL." Expected: do not load Bash references unless shell code appears.
- Compliance checks: output should load only relevant references, avoid command thrash, avoid helper files, preserve marked shell-reality exceptions, and run
bash -n, ShellCheck, and--self-testwhen applicable.
First-class tools
| [TOOL] | [VER] | [PROVIDES] |
|---|---|---|
bash |
5.2+/5.3 | Shell runtime, builtins, ${ } (5.3) |
shellcheck |
0.11.0+ | Static analysis, SC codes |
bats-core |
1.13+ | Test framework, TAP output |
kcov |
43+ | Coverage instrumentation |
rg |
15+ | Content search, .gitignore-aware |
fd |
10+ | File search, .gitignore-aware |
jq |
1.8+ | JSON processing, streaming, trim, skip |
yq |
4.46+ | YAML processing |
mlr |
6+ | CSV/TSV/JSON format transforms |
sd |
1+ | Stream editing (sed replacement) |
choose |
1.3+ | Column selection (cut replacement) |
gawk |
5.3+ | Text processing, inline programs |