Files
small-scripts/memory/gotchas-bash.md
Paul O'Reilly 9e13b7f096 claude-profile: remove provider-yaml system, embed model config in profiles
Provider selection phase removed; Anthropic driver→model_id is now
hardcoded in the script and non-Anthropic profiles supply provider.env
(MODEL_ID, ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY_FILE).

Test suite baseline changes 58 → 41 assertions: the 17 dropped
assertions covered the removed provider-yaml selection phase, not
retained behaviour.

Includes refactor-derived memory: decisions (provider architecture,
driver enforcement via --model), bash gotchas (read -rp under set -e,
never eval user config), process lessons.
2026-06-12 21:26:07 +12:00

2.9 KiB

Bash Gotchas

set -e silently kills scripts with complex subshell pipelines

set -euo pipefail can cause silent termination with no output when scripts use loops containing command substitutions, subshell calls, and pipelines. The script appears to produce no output and exits non-zero. For read-only/reporting tools, use set -uo pipefail without -e and handle errors explicitly.

grep interprets option-like strings as flags

grep -qF "$var" will fail if $var starts with -- (e.g., --dryrun). Always use -- to terminate option parsing: grep -qF -- "$var".

local keyword only works inside functions

Using local in the main script body (e.g., inside a for loop that isn't wrapped in a function) causes local: can only be used in a function. Either wrap the loop in a function or omit local.

set -e kills command substitution capturing non-zero exit codes

output=$(cmd) where cmd exits non-zero will trigger errexit before rc=$? on the next line executes. Use output=$(cmd) && rc=$? || rc=$? or output=$(cmd) || true to safely capture output from commands expected to fail.

((var++)) with var=0 triggers set -e exit

Arithmetic expressions like ((PASS++)) return the pre-increment value. When PASS=0, the result is 0 (falsy), which set -e treats as a failure and silently exits the script. Use PASS=$((PASS + 1)) instead — assignments always succeed regardless of the computed value.

set -e in test harnesses aborts on expected failures

Test scripts with set -euo pipefail silently abort when an assertion helper runs a command expected to fail (non-zero exit). The test runner exits before reporting results. Use set -uo pipefail (no -e) in test harness scripts and check exit codes explicitly in assertion functions.

file command marks shell scripts as "executable"

The file command returns strings like "Bourne-Again shell script, Unicode text, UTF-8 text executable" for shell scripts. Grepping for executable to detect binaries will false-positive on text scripts. Instead, grep for binary|image|archive and additionally check that the output does NOT contain text.

read -rp under set -e exits silently when stdin is not a TTY

read -rp "prompt: " var returns exit code 1 when stdin is closed (no TTY), which set -e treats as a failure and silently kills the script. This makes --dryrun unusable from non-interactive contexts (pipes, CI, test harnesses). Fix: guard every interactive read with [[ -t 0 ]] and fall back to a default value when not on a terminal.

Never eval user-controlled data from config files

eval "$preset_data" is a code-injection footgun if the config file (e.g., presets.yaml) contains shell metacharacters. Instead, parse structured data with awk into known variable names and map them with a case statement. Never eval content that originates from user-editable files.