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.
38 lines
2.9 KiB
Markdown
38 lines
2.9 KiB
Markdown
# 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.
|