Linux Bash Scripting — the meta-skill
Distro support
This meta-skill is how sk-* scripts stay two-family. A script must never
hardcode apt, ufw, or apache2 — it calls the common.sh primitives, which
resolve to the right tool for the detected family (Debian/Ubuntu or the RHEL
family: Fedora, RHEL, CentOS Stream, Rocky, Alma, Oracle).
| Need |
Don't hardcode |
Use this primitive |
| Detect the family |
parse /etc/os-release |
detect_distro → SK_DISTRO_FAMILY, SK_PKG |
| Install packages |
apt install / dnf install |
pkg_install <pkg>... |
| Query installed |
dpkg -s / rpm -q |
pkg_is_installed <pkg> |
| Enable extra repo |
add-apt-repository / EPEL |
ensure_epel (no-op off-RHEL & on Fedora) |
| Service unit name |
apache2 / httpd |
svc_name apache |
| Open a firewall port |
ufw allow / firewall-cmd |
`firewall_allow <port |
| Web config dir / reload |
sites-available / conf.d |
web_conf_dir, web_reload |
| Gate the script |
check $ID |
`require_family <debian |
Full contract: references/common-sh-contract.md.
Plan: docs/multi-distro/plan.md.
Use when
- Writing a new
sk-* script in this repository.
- Reviewing or refactoring an existing script under
scripts/.
- Checking whether a script matches the engine contract, flag model, and safety rules.
Do not use when
- The task is a one-off shell command or an operational fix that will not become a repo script.
- The implementation language is not Bash.
Required Inputs
| Artefact |
Source |
Required? |
If absent |
| Target skill, script name, and inventory entry |
Request and script inventory |
yes |
Stop before scaffolding an unowned script |
| Behaviour, inputs, decision flags, and side effects |
Operator and owning specialist skill |
yes |
Produce a review/questions list; do not invent defaults |
| Distro support and service/package mappings |
Owning skill and common.sh contract |
yes |
Require any only when both families are verified |
| Test fixtures and mutation authority |
Maintainer and safe test host |
for execution |
Limit work to static review and dry-run design |
Capability Contract
Review uses read/search access and is read-only by default. Editing an owned script requires explicit implementation authority; executing privileged or destructive paths requires separate test-host approval. Never use production state to discover what an unsafe script does.
Degraded Mode
Without shellcheck, a Bash runtime, root, or both distro families, complete static checks and label runtime, privilege, or family behaviour unassessed. Do not claim portability or safety from source inspection alone.
Decision Rules
| Choice |
Action |
Failure avoided |
| Input requires a prompt |
Add a named decision flag and require_flag under --yes |
Invented unattended defaults |
| Operation changes important config |
Backup, atomic write, native validation, then reload |
Partial config and outage |
| Package/service differs by family |
Use common.sh primitive |
Debian-only automation |
| Operation is destructive |
Typed confirmation plus explicit non-interactive flag |
Accidental deletion |
Workflow
For every mutating sk-* script, apply the safe reversible operations standard: detect the family, capture a before state, support a dry-run or no-op path, make the operation idempotent, use atomic writes where files change, preserve ownership/labels, and expose a bounded rollback or recovery path. A script is not release-ready until its success path, already-correct path, invalid-input path, and rollback/recovery evidence are tested for both distro families or explicitly marked unassessed.
- Read the owning skill, engine spec, inventory, template, and
common.sh contract.
- Define inputs, effects, decisions, exit codes, rollback, and family support; stop on an unresolved destructive default.
- Scaffold or review the six-section structure and standard flags.
- Implement through shared primitives with quoted inputs, safe temp files, backups, and native validators.
- Test help, bad inputs,
--yes, dry run, interruption, idempotency, and both families where claimed.
- Recover failed mutation from backup, then run shellcheck/engine lint and reconcile manifest documentation.
Evidence Produced
| Artefact |
Acceptance |
| Script validation evidence |
Includes shellcheck/engine lint, help, dry run, negative paths, distro matrix, diff, and manifest alignment |
Quality standards
- Every script must be safe, idempotent by default, and compatible with human and agent callers.
common.sh is mandatory; do not reinvent shared behavior.
- Help output, failure modes, and validation-before-reload rules must be explicit.
Anti-patterns
- Bypassing
common.sh. Fix: use its output, guard, package, service, and file primitives.
- Treating
--yes as permission to invent defaults. Fix: require every decision flag.
- Shipping without a manifest entry. Fix: reconcile the owning skill and inventory.
- Using
set -e. Fix: handle expected failures and preserve set -uo pipefail.
- Writing important files with direct redirection. Fix: back up and use atomic writes.
- Claiming family portability after one-family testing. Fix: test both or mark one unassessed.
Outputs
| Artefact |
Consumer |
Acceptance condition |
| Conforming script or review |
Maintainer |
Contract, flags, safety, and family rules are satisfied |
| Test evidence |
Reviewer |
Covers help, errors, dry run, destructive gates, and lint |
| Manifest update |
Installer |
Script source, name, core status, and purpose agree |
Worked Example
For sk-cifs-mount, make share, mount point, and credentials source explicit flags. Under --yes, missing any required choice exits 2; dry run prints package, credential-file, mount, and fstab actions without exposing the password or writing files.
References
This skill is the foundation every other script in linux-skills is built on.
Before writing a new sk-* script, or reviewing an existing one, load this
skill.
It defines:
- The canonical script template (six-section layout from Pro Bash).
- The
common.sh library contract (what functions exist, what they do).
- The standard flag set every script must support.
- Interactive UX rules (prompts, confirmations, output).
- Safety patterns every script must follow.
- How Claude Code invokes these scripts non-interactively.
Read the full engine spec in docs/engine-design/spec.md
and the script catalogue in docs/engine-design/script-inventory.md
for context.
When to use this skill
- Before writing any new script in
scripts/.
- Before reviewing a script that another person (or another Claude session) wrote.
- When asked "how do I make a script interactive / secure / safe / dry-runnable / help-printing?"
- When unsure which library function to call from
common.sh.
When NOT to use this skill
- For one-liners and ad-hoc commands (don't over-engineer a
grep).
- For Python, Node.js, Go, or any non-Bash tooling.
- For scripts that will never live in this repo (e.g. throwaway fixes).
The canonical script template
Every sk-* script must start from this six-section layout. See
references/script-template.sh for a
copy-pasteable version. To create a new script, the recommended flow is:
sudo sk-new-script <skill-name> <script-name> # scaffolds from template
The six sections in order:
- Metadata header —
#: prefixed comments with Title, Synopsis,
Description, Author, Contact, Version. Grep-extractable. Author line must
always read #: Author: Peter Bamuhigire <techguypeter.com>.
- Library + safety —
set -uo pipefail then source of common.sh.
Never use set -e (it interacts badly with traps and function return
codes).
- Defaults — every tunable as a top-level variable with its default. No
magic numbers deep in the script.
- Functions — helpers, including a
usage() that prints every flag the
script accepts.
- Flag parsing — call
parse_standard_flags "$@", then parse
script-specific decision flags from REMAINING_ARGS.
- Sanity checks —
require_root, require_family <debian|rhel|any>,
require_cmd, and any require_flag calls for --yes mode.
- Main logic — the work, expressed via
header, pass, warn, fail,
info, and run.
The common.sh library contract
common.sh is the shared library every script sources from
/usr/local/lib/linux-skills/common.sh. The full function contract lives in
references/common-sh-contract.md — read
it before calling a new function. Summary:
Output
| Function |
Purpose |
pass "msg" |
Green [PASS] line; increments PASS_COUNT. |
warn "msg" |
Yellow [WARN] line; increments WARN_COUNT. |
fail "msg" |
Red [FAIL] line; increments FAIL_COUNT. |
info "msg" |
Cyan [INFO] line; no count. |
header "Section" |
Bold section separator. |
die "msg" [exit_code] |
Red error to stderr, exit with code (default 1). |
log "msg" |
Timestamped line to log file (if --log). |
Always use these. Never roll your own echo -e "\033[31m...".
Guards
| Function |
Purpose |
require_root |
Exit 1 if not root. |
require_family <debian|rhel|any> |
Exit 3 if the detected family isn't allowed. Gate a script to one family, or pass any for both. Prefer this over the legacy require_debian. |
require_cmd <cmd>... |
Exit 5 if any command is missing; names the package. |
require_flag <NAME> |
Under --yes, exit 2 if the named global variable is empty. |
Interaction (skipped under --yes)
| Function |
Purpose |
confirm "Do X?" [default=N] |
yes/no; default = N. Auto-yes under --yes only for non-destructive confirms. |
confirm_destructive "About to DELETE X" |
Requires typed yes, not y. Under --yes, requires a decision flag — never auto-confirms. |
prompt "Label" [default] [validator] |
Single-value read with optional default + validator. |
select_one "Label" opt1 opt2... |
Numeric menu; returns chosen value. |
Safe file operations
| Function |
Purpose |
safe_tempfile [prefix] |
mktemp + trap cleanup. Returns path. |
safe_tempdir [prefix] |
As above, directory. |
atomic_write <target> |
Reads stdin, writes <target>.new, mv on success. Preserves perms/owner. |
backup_file <path> |
Copies to <path>.bak-YYYYMMDD-HHMMSS. Prints backup path. |
Flag parsing
| Function |
Purpose |
parse_standard_flags "$@" |
Consumes standard flags, sets DRY_RUN, YES, LOG_FILE, JSON, VERBOSE, QUIET. Leaves unknown args in REMAINING_ARGS. |
run <cmd>... |
Prints the command. In --dry-run, prints and skips. |
Standard flags — mandatory on every script
Every script must support these flags via parse_standard_flags:
| Flag |
Meaning |
--help, -h |
Print usage() and exit 0. Must list every decision flag. |
--version |
Print version and exit 0. |
--yes, -y |
Non-interactive. Errors if a required decision flag is missing — never silently defaults. |
--dry-run, -n |
Print every action, change nothing. |
--log[=PATH] |
Tee output to /var/log/linux-skills/<script>-YYYYMMDD-HHMMSS.log. |
--json |
Machine-readable output (where meaningful). No colors. |
--verbose, -v |
Extra diagnostics. |
--quiet, -q |
Errors and final result only. Incompatible with -v. |
The --yes contract (non-negotiable)
--yes means "the caller has pre-committed to every decision". It does
not mean "accept safe defaults." If a script would normally prompt for a
required input and the caller did not supply a flag for it, the script must
abort:
ERROR: --yes was passed but --domain is required.
Run `<script> --help` to see required flags.
This rule exists because Claude Code invokes scripts non-interactively and
must get exactly the outcome it asked for — silent defaults are a footgun.
Interactive UX rules
When running interactively (no --yes), scripts must feel friendly at a
terminal. See references/interactive-ux.md
for the full rule set. Key points:
- Announce before acting. Every destructive step prints what it is about
to do, then asks to confirm.
- Show progress. Long-running steps use
header to mark phases.
- PASS / WARN / FAIL is the output grammar. Use
pass, warn, fail,
info — never ad-hoc echo.
- Explain failures. When a check fails, say why in plain English and
point at the remediation (another
sk-* script, a config file, a man page).
- Colors are semantic. Green = good; yellow = watch; red = broken;
cyan = neutral information. Never decorate.
- Confirm destructive operations with a typed word, not a single letter.
Use
confirm_destructive, which requires the user to type yes.
Ctrl-C is a first-class exit path. The trap installed by common.sh
ensures a clean exit. Don't fight it.
- Help text must be complete. Every decision flag listed. Examples
included. Fits on one screen where possible.
Safety patterns every script must follow
These rules are enforced by sk-lint (the pre-commit linter) and checked in
code review. Violations block a merge.
- Source
common.sh — never reimplement colors, prompts, traps.
set -uo pipefail — always. Never set -e.
- Quote every variable —
"$var", "${array[@]}", always.
IFS= read -r — every read, every time.
- Parameter expansion over sed/awk —
${var##*/} for basename,
${var%.*} for removing a suffix. Faster and safer than subprocessing.
- Atomic file edits — write to
$target.new, then mv. Never > file
on anything important. Use atomic_write or backup_file first.
- Safe temp files —
safe_tempfile, which auto-registers a cleanup trap.
- Validate with
case, not sprawling regex — from Pro Bash. Easier to
read, easier to extend, easier to debug.
printf over echo — printf '%s\n' "$var". echo's behavior varies.
"$@" (quoted) when forwarding args. Unquoted $@ word-splits.
- Exit codes matter —
0 success, 1 generic failure, 2
usage/flag error, 3 precondition failed, 4 user aborted, 5
dependency missing.
- Never
eval untrusted input. No exceptions.
- Validate external configs before reload —
nginx -t, apache2ctl configtest, visudo -c, sshd -t, named-checkconf. Every time.
- Destructive operations write a timestamped audit line to
/var/log/linux-skills/<script>.log regardless of --log.
- Every script passes
shellcheck with zero warnings. Run sk-lint
before committing.
Scripts
This skill installs the following scripts to /usr/local/bin/. To install:
sudo install-skills-bin linux-bash-scripting
| Script |
Source |
Core? |
Purpose |
| sk-new-script |
scripts/sk-new-script.sh |
yes |
Scaffold a new sk-* script from the canonical template in a skill's scripts/ directory. |
| sk-lint |
scripts/sk-lint.sh |
yes |
Run shellcheck plus custom engine checks (standard flags present, common.sh sourced, no set -e, no unquoted vars) on one or more scripts. |
How Claude Code invokes these scripts
When Claude Code is asked to do something that maps to a script in this
toolkit, it must:
- Discover — check
command -v sk-foo to see if the script is
installed. If not, suggest sudo install-skills-bin <skill-name>.
- Read help — run
sk-foo --help to see the required decision flags.
- Pre-commit — decide every required input up front (domain, user,
port, path, etc.). Never call with
--yes and hope a default saves the day.
- Dry-run first — when uncertain, run with
--dry-run --yes --<flags>
to preview.
- Invoke — run with
--yes --log --<flags>.
- Interpret — parse PASS/WARN/FAIL counts from output; if
--json is
supported, use it.
If any step of this loop isn't possible (e.g. the script doesn't have the
flag we need), that's a script bug — file it against the inventory, not a
silent workaround.
References
references/script-template.sh — the canonical six-section template.
references/common-sh-contract.md — full function contract for the shared library.
references/interactive-ux.md — interactive UX rules in detail.
docs/engine-design/spec.md — the binding engine specification.
docs/engine-design/script-inventory.md — the curated catalogue.
1---2name: linux-bash-scripting3description: Use when writing or reviewing portable sk-* Bash scripts for this engine, including common.sh primitives, flags, dry runs, and safety gates; use linux-repo-sync for Git-update behaviour and not for one-off commands.4license: MIT5---67# Linux Bash Scripting — the meta-skill89## Distro support1011This meta-skill is **how** sk-* scripts stay two-family. A script must never12hardcode `apt`, `ufw`, or `apache2` — it calls the `common.sh` primitives, which13resolve to the right tool for the detected family (Debian/Ubuntu or the RHEL14family: Fedora, RHEL, CentOS Stream, Rocky, Alma, Oracle).1516| Need | Don't hardcode | Use this primitive |17|---|---|---|18| Detect the family | parse `/etc/os-release` | `detect_distro` → `SK_DISTRO_FAMILY`, `SK_PKG` |19| Install packages | `apt install` / `dnf install` | `pkg_install <pkg>...` |20| Query installed | `dpkg -s` / `rpm -q` | `pkg_is_installed <pkg>` |21| Enable extra repo | `add-apt-repository` / EPEL | `ensure_epel` (no-op off-RHEL & on Fedora) |22| Service unit name | `apache2` / `httpd` | `svc_name apache` |23| Open a firewall port | `ufw allow` / `firewall-cmd` | `firewall_allow <port|service>` |24| Web config dir / reload | `sites-available` / `conf.d` | `web_conf_dir`, `web_reload` |25| Gate the script | check `$ID` | `require_family <debian|rhel|any>` |2627Full contract: [`references/common-sh-contract.md`](references/common-sh-contract.md).28Plan: [`docs/multi-distro/plan.md`](../../docs/multi-distro/plan.md).2930<!-- dual-compat-start -->3132## Use when3334- Writing a new `sk-*` script in this repository.35- Reviewing or refactoring an existing script under `scripts/`.36- Checking whether a script matches the engine contract, flag model, and safety rules.3738## Do not use when3940- The task is a one-off shell command or an operational fix that will not become a repo script.41- The implementation language is not Bash.4243## Required Inputs4445| Artefact | Source | Required? | If absent |46|---|---|---|---|47| Target skill, script name, and inventory entry | Request and script inventory | yes | Stop before scaffolding an unowned script |48| Behaviour, inputs, decision flags, and side effects | Operator and owning specialist skill | yes | Produce a review/questions list; do not invent defaults |49| Distro support and service/package mappings | Owning skill and `common.sh` contract | yes | Require `any` only when both families are verified |50| Test fixtures and mutation authority | Maintainer and safe test host | for execution | Limit work to static review and dry-run design |5152## Capability Contract5354Review uses read/search access and is read-only by default. Editing an owned script requires explicit implementation authority; executing privileged or destructive paths requires separate test-host approval. Never use production state to discover what an unsafe script does.5556## Degraded Mode5758Without shellcheck, a Bash runtime, root, or both distro families, complete static checks and label runtime, privilege, or family behaviour unassessed. Do not claim portability or safety from source inspection alone.5960## Decision Rules6162| Choice | Action | Failure avoided |63|---|---|---|64| Input requires a prompt | Add a named decision flag and `require_flag` under `--yes` | Invented unattended defaults |65| Operation changes important config | Backup, atomic write, native validation, then reload | Partial config and outage |66| Package/service differs by family | Use `common.sh` primitive | Debian-only automation |67| Operation is destructive | Typed confirmation plus explicit non-interactive flag | Accidental deletion |6869## Workflow7071For every mutating `sk-*` script, apply the safe reversible operations standard: detect the family, capture a before state, support a dry-run or no-op path, make the operation idempotent, use atomic writes where files change, preserve ownership/labels, and expose a bounded rollback or recovery path. A script is not release-ready until its success path, already-correct path, invalid-input path, and rollback/recovery evidence are tested for both distro families or explicitly marked unassessed.72731. Read the owning skill, engine spec, inventory, template, and `common.sh` contract.742. Define inputs, effects, decisions, exit codes, rollback, and family support; stop on an unresolved destructive default.753. Scaffold or review the six-section structure and standard flags.764. Implement through shared primitives with quoted inputs, safe temp files, backups, and native validators.775. Test help, bad inputs, `--yes`, dry run, interruption, idempotency, and both families where claimed.786. Recover failed mutation from backup, then run shellcheck/engine lint and reconcile manifest documentation.7980## Evidence Produced8182| Artefact | Acceptance |83|---|---|84| Script validation evidence | Includes shellcheck/engine lint, help, dry run, negative paths, distro matrix, diff, and manifest alignment |8586## Quality standards8788- Every script must be safe, idempotent by default, and compatible with human and agent callers.89- `common.sh` is mandatory; do not reinvent shared behavior.90- Help output, failure modes, and validation-before-reload rules must be explicit.9192## Anti-patterns9394- Bypassing `common.sh`. Fix: use its output, guard, package, service, and file primitives.95- Treating `--yes` as permission to invent defaults. Fix: require every decision flag.96- Shipping without a manifest entry. Fix: reconcile the owning skill and inventory.97- Using `set -e`. Fix: handle expected failures and preserve `set -uo pipefail`.98- Writing important files with direct redirection. Fix: back up and use atomic writes.99- Claiming family portability after one-family testing. Fix: test both or mark one unassessed.100101## Outputs102103| Artefact | Consumer | Acceptance condition |104|---|---|---|105| Conforming script or review | Maintainer | Contract, flags, safety, and family rules are satisfied |106| Test evidence | Reviewer | Covers help, errors, dry run, destructive gates, and lint |107| Manifest update | Installer | Script source, name, core status, and purpose agree |108109## Worked Example110111For `sk-cifs-mount`, make share, mount point, and credentials source explicit flags. Under `--yes`, missing any required choice exits 2; dry run prints package, credential-file, mount, and fstab actions without exposing the password or writing files.112113<!-- dual-compat-end -->114115## References116117- [`../../docs/continuous-improvement/safe-reversible-operations-standard.md`](../../docs/continuous-improvement/safe-reversible-operations-standard.md)118- [`../../docs/continuous-improvement/two-family-validation-and-recovery.md`](../../docs/continuous-improvement/two-family-validation-and-recovery.md)119120- [`references/script-template.sh`](references/script-template.sh)121- [`references/common-sh-contract.md`](references/common-sh-contract.md)122- [`references/interactive-ux.md`](references/interactive-ux.md)123- [`docs/engine-design/spec.md`](../../docs/engine-design/spec.md)124125This skill is the foundation every other script in `linux-skills` is built on.126Before writing a new `sk-*` script, or reviewing an existing one, load this127skill.128129It defines:1301311. The canonical script template (six-section layout from *Pro Bash*).1322. The `common.sh` library contract (what functions exist, what they do).1333. The standard flag set every script must support.1344. Interactive UX rules (prompts, confirmations, output).1355. Safety patterns every script must follow.1366. How Claude Code invokes these scripts non-interactively.137138Read the full engine spec in [`docs/engine-design/spec.md`](../../docs/engine-design/spec.md)139and the script catalogue in [`docs/engine-design/script-inventory.md`](../../docs/engine-design/script-inventory.md)140for context.141142---143144## When to use this skill145146- Before writing any new script in `scripts/`.147- Before reviewing a script that another person (or another Claude session) wrote.148- When asked "how do I make a script interactive / secure / safe / dry-runnable / help-printing?"149- When unsure which library function to call from `common.sh`.150151## When NOT to use this skill152153- For one-liners and ad-hoc commands (don't over-engineer a `grep`).154- For Python, Node.js, Go, or any non-Bash tooling.155- For scripts that will never live in this repo (e.g. throwaway fixes).156157---158159## The canonical script template160161Every `sk-*` script must start from this six-section layout. See162[`references/script-template.sh`](references/script-template.sh) for a163copy-pasteable version. To create a new script, the recommended flow is:164165```bash166sudo sk-new-script <skill-name> <script-name> # scaffolds from template167```168169The six sections in order:1701711. **Metadata header** — `#:` prefixed comments with Title, Synopsis,172 Description, Author, Contact, Version. Grep-extractable. Author line must173 always read `#: Author: Peter Bamuhigire <techguypeter.com>`.1742. **Library + safety** — `set -uo pipefail` then `source` of `common.sh`.175 Never use `set -e` (it interacts badly with traps and function return176 codes).1773. **Defaults** — every tunable as a top-level variable with its default. No178 magic numbers deep in the script.1794. **Functions** — helpers, including a `usage()` that prints every flag the180 script accepts.1815. **Flag parsing** — call `parse_standard_flags "$@"`, then parse182 script-specific decision flags from `REMAINING_ARGS`.1836. **Sanity checks** — `require_root`, `require_family <debian|rhel|any>`,184 `require_cmd`, and any `require_flag` calls for `--yes` mode.1857. **Main logic** — the work, expressed via `header`, `pass`, `warn`, `fail`,186 `info`, and `run`.187188---189190## The `common.sh` library contract191192`common.sh` is the shared library every script sources from193`/usr/local/lib/linux-skills/common.sh`. The full function contract lives in194[`references/common-sh-contract.md`](references/common-sh-contract.md) — read195it before calling a new function. Summary:196197### Output198199| Function | Purpose |200|---|---|201| `pass "msg"` | Green `[PASS]` line; increments `PASS_COUNT`. |202| `warn "msg"` | Yellow `[WARN]` line; increments `WARN_COUNT`. |203| `fail "msg"` | Red `[FAIL]` line; increments `FAIL_COUNT`. |204| `info "msg"` | Cyan `[INFO]` line; no count. |205| `header "Section"` | Bold section separator. |206| `die "msg" [exit_code]` | Red error to stderr, exit with code (default 1). |207| `log "msg"` | Timestamped line to log file (if `--log`). |208209Always use these. Never roll your own `echo -e "\033[31m..."`.210211### Guards212213| Function | Purpose |214|---|---|215| `require_root` | Exit 1 if not root. |216| `require_family <debian\|rhel\|any>` | Exit 3 if the detected family isn't allowed. Gate a script to one family, or pass `any` for both. Prefer this over the legacy `require_debian`. |217| `require_cmd <cmd>...` | Exit 5 if any command is missing; names the package. |218| `require_flag <NAME>` | Under `--yes`, exit 2 if the named global variable is empty. |219220### Interaction (skipped under `--yes`)221222| Function | Purpose |223|---|---|224| `confirm "Do X?" [default=N]` | yes/no; default = N. Auto-yes under `--yes` **only** for non-destructive confirms. |225| `confirm_destructive "About to DELETE X"` | Requires typed `yes`, not `y`. Under `--yes`, requires a decision flag — **never** auto-confirms. |226| `prompt "Label" [default] [validator]` | Single-value read with optional default + validator. |227| `select_one "Label" opt1 opt2...` | Numeric menu; returns chosen value. |228229### Safe file operations230231| Function | Purpose |232|---|---|233| `safe_tempfile [prefix]` | `mktemp` + trap cleanup. Returns path. |234| `safe_tempdir [prefix]` | As above, directory. |235| `atomic_write <target>` | Reads stdin, writes `<target>.new`, `mv` on success. Preserves perms/owner. |236| `backup_file <path>` | Copies to `<path>.bak-YYYYMMDD-HHMMSS`. Prints backup path. |237238### Flag parsing239240| Function | Purpose |241|---|---|242| `parse_standard_flags "$@"` | Consumes standard flags, sets `DRY_RUN`, `YES`, `LOG_FILE`, `JSON`, `VERBOSE`, `QUIET`. Leaves unknown args in `REMAINING_ARGS`. |243| `run <cmd>...` | Prints the command. In `--dry-run`, prints and skips. |244245---246247## Standard flags — mandatory on every script248249Every script must support these flags via `parse_standard_flags`:250251| Flag | Meaning |252|---|---|253| `--help`, `-h` | Print `usage()` and exit 0. Must list every decision flag. |254| `--version` | Print version and exit 0. |255| `--yes`, `-y` | Non-interactive. **Errors** if a required decision flag is missing — never silently defaults. |256| `--dry-run`, `-n` | Print every action, change nothing. |257| `--log[=PATH]` | Tee output to `/var/log/linux-skills/<script>-YYYYMMDD-HHMMSS.log`. |258| `--json` | Machine-readable output (where meaningful). No colors. |259| `--verbose`, `-v` | Extra diagnostics. |260| `--quiet`, `-q` | Errors and final result only. Incompatible with `-v`. |261262### The `--yes` contract (non-negotiable)263264`--yes` means **"the caller has pre-committed to every decision"**. It does265**not** mean "accept safe defaults." If a script would normally prompt for a266required input and the caller did not supply a flag for it, the script must267abort:268269```270ERROR: --yes was passed but --domain is required.271 Run `<script> --help` to see required flags.272```273274This rule exists because Claude Code invokes scripts non-interactively and275must get exactly the outcome it asked for — silent defaults are a footgun.276277---278279## Interactive UX rules280281When running interactively (no `--yes`), scripts must feel friendly at a282terminal. See [`references/interactive-ux.md`](references/interactive-ux.md)283for the full rule set. Key points:284285- **Announce before acting.** Every destructive step prints what it is about286 to do, then asks to confirm.287- **Show progress.** Long-running steps use `header` to mark phases.288- **PASS / WARN / FAIL is the output grammar.** Use `pass`, `warn`, `fail`,289 `info` — never ad-hoc `echo`.290- **Explain failures.** When a check fails, say *why* in plain English and291 point at the remediation (another `sk-*` script, a config file, a man page).292- **Colors are semantic.** Green = good; yellow = watch; red = broken;293 cyan = neutral information. Never decorate.294- **Confirm destructive operations with a typed word, not a single letter.**295 Use `confirm_destructive`, which requires the user to type `yes`.296- **`Ctrl-C` is a first-class exit path.** The trap installed by `common.sh`297 ensures a clean exit. Don't fight it.298- **Help text must be complete.** Every decision flag listed. Examples299 included. Fits on one screen where possible.300301---302303## Safety patterns every script must follow304305These rules are enforced by `sk-lint` (the pre-commit linter) and checked in306code review. Violations block a merge.3073081. **Source `common.sh`** — never reimplement colors, prompts, traps.3092. **`set -uo pipefail`** — always. **Never** `set -e`.3103. **Quote every variable** — `"$var"`, `"${array[@]}"`, always.3114. **`IFS= read -r`** — every `read`, every time.3125. **Parameter expansion over sed/awk** — `${var##*/}` for basename,313 `${var%.*}` for removing a suffix. Faster and safer than subprocessing.3146. **Atomic file edits** — write to `$target.new`, then `mv`. Never `> file`315 on anything important. Use `atomic_write` or `backup_file` first.3167. **Safe temp files** — `safe_tempfile`, which auto-registers a cleanup trap.3178. **Validate with `case`, not sprawling regex** — from *Pro Bash*. Easier to318 read, easier to extend, easier to debug.3199. **`printf` over `echo`** — `printf '%s\n' "$var"`. `echo`'s behavior varies.32010. **`"$@"` (quoted)** when forwarding args. Unquoted `$@` word-splits.32111. **Exit codes matter** — `0` success, `1` generic failure, `2`322 usage/flag error, `3` precondition failed, `4` user aborted, `5`323 dependency missing.32412. **Never `eval` untrusted input.** No exceptions.32513. **Validate external configs before reload** — `nginx -t`, `apache2ctl326 configtest`, `visudo -c`, `sshd -t`, `named-checkconf`. Every time.32714. **Destructive operations write a timestamped audit line** to328 `/var/log/linux-skills/<script>.log` regardless of `--log`.32915. **Every script passes `shellcheck` with zero warnings.** Run `sk-lint`330 before committing.331332---333334## Scripts335336This skill installs the following scripts to `/usr/local/bin/`. To install:337338```bash339sudo install-skills-bin linux-bash-scripting340```341342| Script | Source | Core? | Purpose |343|---|---|---|---|344| sk-new-script | scripts/sk-new-script.sh | yes | Scaffold a new `sk-*` script from the canonical template in a skill's `scripts/` directory. |345| sk-lint | scripts/sk-lint.sh | yes | Run `shellcheck` plus custom engine checks (standard flags present, `common.sh` sourced, no `set -e`, no unquoted vars) on one or more scripts. |346347---348349## How Claude Code invokes these scripts350351When Claude Code is asked to do something that maps to a script in this352toolkit, it must:3533541. **Discover** — check `command -v sk-foo` to see if the script is355 installed. If not, suggest `sudo install-skills-bin <skill-name>`.3562. **Read help** — run `sk-foo --help` to see the required decision flags.3573. **Pre-commit** — decide every required input up front (domain, user,358 port, path, etc.). Never call with `--yes` and hope a default saves the day.3594. **Dry-run first** — when uncertain, run with `--dry-run --yes --<flags>`360 to preview.3615. **Invoke** — run with `--yes --log --<flags>`.3626. **Interpret** — parse PASS/WARN/FAIL counts from output; if `--json` is363 supported, use it.364365If any step of this loop isn't possible (e.g. the script doesn't have the366flag we need), that's a script bug — file it against the inventory, not a367silent workaround.368369---370371## References372373- [`references/script-template.sh`](references/script-template.sh) — the canonical six-section template.374- [`references/common-sh-contract.md`](references/common-sh-contract.md) — full function contract for the shared library.375- [`references/interactive-ux.md`](references/interactive-ux.md) — interactive UX rules in detail.376- [`docs/engine-design/spec.md`](../../docs/engine-design/spec.md) — the binding engine specification.377- [`docs/engine-design/script-inventory.md`](../../docs/engine-design/script-inventory.md) — the curated catalogue.