Skip to content

Commit 97cc6ad

Browse files
committed
tasks
1 parent 03bfc07 commit 97cc6ad

2 files changed

Lines changed: 50 additions & 55 deletions

File tree

tasks/lessons.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Lessons
2+
3+
## Bash: no apostrophes / single quotes inside `${var:-default}` defaults
4+
5+
**Symptom:** `bash -n install-caddy.sh` reported `syntax error near unexpected
6+
token '('` on a line whose `(` was harmlessly inside a double-quoted string — and
7+
the *real* offending code was ~14 lines earlier.
8+
9+
**Cause:** a default value contained an apostrophe:
10+
`note="... (-> ${pub:-this host's inbound public IP}) ..."`. Even inside double
11+
quotes and inside `${...:-...}`, bash treats that `'` as a quote delimiter and pairs
12+
it with the *next* single quote in the file (here, the opening `'` of a later
13+
`printf '{...}'` format string). Everything between is mis-quoted, desyncing all the
14+
double-quote accounting, so the parser blows up on a much later line.
15+
16+
**Rule:** never put `'` (including apostrophes in prose) inside a `${var:-word}` /
17+
`:+` / `:=` default. Rephrase to avoid the apostrophe. The codebase already follows
18+
this — e.g. `${pub:-this servers public IP}` (no apostrophe in "servers"). Match it.
19+
20+
**Bonus:** when `bash -n` points at a line that looks obviously fine, suspect an
21+
unbalanced quote *earlier* in the file, not the reported line.
22+
23+
## Bash: `local a; a="$(cmd)" b` runs `b` as a command and drops `a`
24+
25+
**Symptom:** shellcheck SC2154 ("b is referenced but not assigned") on a resolver I
26+
wrote as `local etc; etc="$(_tn_etc)" var; ...; printf '%s' "$var"` — and at runtime
27+
`$var`/`$etc` would have been **empty**. `bash -n` passed (valid syntax) so it nearly
28+
slipped through; only shellcheck + a fixture unit test caught it.
29+
30+
**Cause:** `etc="$(_tn_etc)" var` is parsed as *run the command `var` with the env var
31+
`etc` set for that one command*. Prefix assignments are temporary — `etc` is NOT
32+
retained in the shell afterward — and `var` (or `t`, `n`, …) is executed as a bogus
33+
command. Six resolver functions had this shape; every one was silently broken.
34+
35+
**Rule:** declare all locals on the `local` line, then assign on their own lines:
36+
`local etc var; etc="$(_tn_etc)"; var="$(_tn_var)"`. Never trail a bare word after a
37+
`VAR="$(...)"` assignment expecting it to be another local. Always shellcheck +
38+
fixture-test library resolvers before building consumers on top of them.
39+
40+
## Verifying bash scripts that gate on `check_root` / do real side effects
41+
42+
To unit-test functions in a script that ends with `main "$@"` and whose interactive
43+
path calls `check_root` + real installers: strip the trailing `main "$@"` into a temp
44+
copy (`grep -v '^main "\$@"$'`), symlink `lib/` next to it so `source lib/common.sh`
45+
resolves, `source` it, then override the side-effecting/network functions with stubs
46+
and call the target function directly. Drive `read` prompts via piped stdin. Note:
47+
`read -p` shows its prompt **only when stdin is a TTY**, so a piped harness won't see
48+
prompt text — assert on resulting behavior/state instead. For network-dependent
49+
helpers (curl/dig/hostname), prefer PATH shims that echo env-var-controlled values so
50+
every branch is deterministic.

tasks/todo.md

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)