shell-scripting
Shell script conventions, defensive patterns, and correctness rules: strict mode, quoting, portability, error handling, and common pitfalls. Invoke whenever task involves any interaction with shell scripts — writing, reviewing, debugging, or understanding .sh, .bash, .zsh files.
What this skill does
# Shell Scripting
Write defensively. Shell defaults are hostile — unquoted variables split, unset variables vanish silently, failed
commands continue. Every rule here exists to counteract a specific shell default that causes bugs.
## References
Extended examples, code patterns, and lookup tables for the rules below.
- **Strict mode, error handling, traps, debugging** — [`${CLAUDE_SKILL_DIR}/references/strict-mode.md`]: errexit
caveats, pipefail examples, trap patterns, temp file safety, debugging techniques
- **Quoting rules, word splitting, globbing** — [`${CLAUDE_SKILL_DIR}/references/quoting.md`]: Three quoting mechanisms,
`"$@"` vs `"$*"`, array expansion, printf vs echo, nested quoting
- **POSIX sh vs bash, portable constructs** — [`${CLAUDE_SKILL_DIR}/references/portability.md`]: Feature comparison, GNU
vs BSD tool differences, portable pattern catalog
- **Argument parsing, getopts, validation** — [`${CLAUDE_SKILL_DIR}/references/arguments.md`]: getopts template, manual
long-option parsing, validation patterns, usage messages, stdin detection
- **Common shell scripting mistakes** — [`${CLAUDE_SKILL_DIR}/references/pitfalls.md`]: Iteration pitfalls, variable
pitfalls, test pitfalls, pipeline pitfalls, arithmetic traps
- **Pure bash/sh alternatives to external commands** — [`${CLAUDE_SKILL_DIR}/references/builtins.md`]: Parameter
expansion, replacing sed/cut/basename/expr, arrays, read patterns, arithmetic
## Script Header
Every bash script starts with:
```bash
#!/usr/bin/env bash
set -euo pipefail
```
- **Shebang:** Use `#!/usr/bin/env bash` — not `#!/bin/bash`. The `env` lookup is more portable across systems where
bash is not at `/bin/bash`.
- **`set -e` (errexit):** Exit on command failure. Understand the exceptions: commands in `if`/`while` conditions, left
side of `&&`/`||`, and negated commands (`!`) do not trigger errexit.
- **`set -u` (nounset):** Error on unset variables. Use `${VAR:-default}` for optional variables.
- **`set -o pipefail`:** Pipeline returns the rightmost failing command's exit code, not the last command's.
- **For POSIX sh scripts:** Use `#!/bin/sh`. Drop `pipefail` (not POSIX). Use `set -eu` with caution — `set -e` behavior
varies across sh implementations.
- **File header comment:** After the shebang, add a brief description of what the script does.
```bash
#!/usr/bin/env bash
set -euo pipefail
#
# deploy.sh — Build and deploy the application to staging.
```
## Quoting
Quoting is the single most important discipline. Unquoted variables undergo word splitting (breaks on IFS characters)
and pathname expansion (glob characters match filenames). Both are silent and devastating.
### Core Rules
- **Always double-quote variable expansions:** `"$var"`, `"${var}"`.
- **Always double-quote command substitutions:** `"$(command)"`.
- **Use `"$@"` to pass arguments through.** Never `$*` or `$@` unquoted. `"$@"` preserves each argument as a separate
word. `"$*"` joins them.
- **Quote array expansions:** `"${arr[@]}"` expands each element as a separate word. Unquoted `${arr[@]}` undergoes word
splitting.
- **Leave globs unquoted:** `for f in *.txt` — the glob must expand. But always quote variables inside the loop: `"$f"`.
- **Leave `[[ ]]` right-hand patterns unquoted** when doing glob or regex matching. Quote the right side for literal
string comparison.
- **Use single quotes for literal strings** that need no expansion: `grep 'pattern' file`.
- **Use `printf` instead of `echo`** for data output. `echo` interprets `-n`, `-e` as options on some platforms.
`printf '%s\n' "$var"` is always safe.
### When Quoting Is Not Needed
- Right side of assignment: `var=$other` (no splitting in assignment context)
- Inside `(( ))` arithmetic: `(( x + y ))`
- Inside `[[ ]]` on the left side: `[[ $var == pattern ]]`
- Integer special variables: `$?`, `$#`, `$$` (guaranteed no spaces)
- `case` word: `case $var in ...`
## Variable Handling
- **Naming:** lowercase with underscores for local variables (`file_path`, `line_count`). UPPER_CASE for
exported/environment variables and constants (`PATH`, `MAX_RETRIES`).
- **Declare constants with `readonly`:**
```bash
readonly CONFIG_DIR="/etc/myapp"
```
- **Use `local` in functions** to prevent variable leakage into global scope. Declare and assign on separate lines when
capturing command output:
```bash
local result
result=$(some_command)
```
Combined `local result=$(cmd)` masks the exit code — `local` always returns 0.
- **Default values:** Use `${VAR:-default}` to provide defaults without modifying the variable. Use `${VAR:=default}` to
set and use.
- **Required variables:** Use `${VAR:?error message}` to abort if unset.
- **Arrays for lists:** Use bash arrays instead of space-delimited strings.
```bash
files=("file one.txt" "file two.txt")
command "${files[@]}"
```
## Error Handling
- **Check every command that can fail.** Use `|| exit 1`, `|| return 1`, or explicit `if` blocks. Especially `cd`,
`mkdir`, `rm`, `cp`, `mv`.
```bash
cd "$dir" || exit 1
```
- **Trap for cleanup.** Use `trap` on EXIT for reliable cleanup:
```bash
tmpfile=$(mktemp) || exit 1
trap 'rm -f "$tmpfile"' EXIT
```
- **Use `mktemp` for temp files.** Never hardcoded temp paths. Always clean up via trap.
- **Error messages to stderr:**
```bash
die() { printf '%s\n' "$1" >&2; exit "${2:-1}"; }
```
- **Exit codes:** Return 0 for success, non-zero for failure. Use meaningful codes: 1 for general error, 2 for usage
error, 64+ for application-specific errors (following sysexits convention).
- **Never use `set -e` as a substitute for error handling.** It has many edge cases. Use it as a safety net, but still
check critical commands explicitly.
## Functions
- **Declare with `name() { ... }`** — no `function` keyword (it's not POSIX and adds nothing in bash).
- **Use `local` for all function variables.** Bash functions share the caller's scope by default — every undeclared
variable is global.
- **Return values via exit code** (0 = success, non-zero = failure) or via stdout. Never rely on global variables for
function output.
- **Separate `local` declaration from command substitution:**
```bash
my_func() {
local output
output=$(some_command) || return 1
}
```
- **Put all functions before executable code.** Only `set` statements, source commands, and constants should precede
function definitions.
- **Use `main` for scripts with multiple functions.** Call `main "$@"` as the last line. This keeps the entry point
obvious and lets all variables be local.
```bash
main() {
local arg="$1"
# ...
}
main "$@"
```
## Control Flow
### Conditionals
- **Use `[[ ]]` in bash** — it prevents word splitting, supports `&&`/`||` inside the test, and enables pattern/regex
matching. In POSIX sh, use `[ ]` with all variables quoted.
- **Use `(( ))` for numeric comparisons:**
```bash
if (( count > 10 )); then ...
```
In POSIX sh: `[ "$count" -gt 10 ]`.
- **Use `==` in `[[ ]]` and `=` in `[ ]`** for string equality.
- **Test empty/non-empty explicitly:** `[[ -z "$var" ]]` and `[[ -n "$var" ]]` — not `[[ "$var" ]]`.
- **Never use `&&`/`||` as if/then/else:**
```bash
# WRONG — cmd3 runs if cmd2 fails, even when cmd1 succeeds
cmd1 && cmd2 || cmd3
# RIGHT
if cmd1; then cmd2; else cmd3; fi
```
### Loops
- **Never parse `ls` output.** Use globs:
```bash
for f in ./*.txt; do
[[ -e "$f" ]] || continue
process "$f"
done
```
- **Use `while read` for line-oriented input:**
```bash
while IFS= read -r line; do
printf '%s\n' "$line"
done < file
```
The `IFS=` prevents leading/trailing whitespace trimming. The `-r` prevents backslash interpretation.
- **Use process substitution to avoid subshell variable loss:**
```bash
while IFS= read -r line; do
(( count++ ))
done < <(command)
echo "$count" # preserved
```
- **Use `find -print0` with `read -d ''`** for filenames witRelated in Productivity
gitea-workflow
IncludedOrchestrate agile development workflows for Gitea repositories using the tea CLI. Use when working with Gitea-hosted repos and asking to 'run the workflow', 'continue working', 'what's next', 'complete the task cycle', 'start my day', 'end the sprint', 'implement the next task', or wanting guided step-by-step development assistance. Keywords: workflow, orchestrate, agile, task cycle, sprint, daily, implement, review, PR, standup, retrospective, gitea, tea.
microsoft-graph-gateway
IncludedRoute Microsoft Graph work in this workspace. Use when users want to read or write Outlook mail, calendar events, contacts, OneDrive or SharePoint files, Teams, Planner, To Do, users, groups, directory data, or arbitrary Microsoft Graph endpoints from VS Code. Prefer WorkIQ for common read scenarios. Use Microsoft Graph for write actions and gap-read scenarios that need exact Graph properties, filters, permissions, or endpoints.
copilotkit
IncludedUse when building with CopilotKit — setup, development, integrations, debugging, upgrading, or contributing. Routes to the appropriate specialized skill based on the task.
wordly-wisdom
IncludedProvides calibrated decision analysis using Charlie Munger-style multiple mental models, inversion, incentive mapping, circle-of-competence checks, misjudgment audits, second-order effects, and forecast updates. Use when the user asks for an oracle take, a hard call, a decision memo, a premortem, an outside view, a red-team, a sanity-check, what am I missing, think this through, or wants a strategy, hire, investment, plan, product, partnership, or major life choice analysed. Avoid for simple factual lookups or time-sensitive legal, medical, or market questions without fresh evidence.
swain-session
IncludedSession management and project status dashboard. Owns the full session lifecycle (start/work/close/resume), focus lane, bookmarks, worktree detection, and tab naming. Also serves as the project status dashboard — shows active epics, progress, actionable next steps, blocked items, tasks, GitHub issues, and recommendations. Worktree creation is deferred to swain-do task dispatch (SPEC-195). Triggers on: 'session', 'status', 'what's next', 'dashboard', 'overview', 'where are we', 'what should I work on', 'show me priorities', 'bookmark', 'focus on', 'session info'.
gandi
IncludedComprehensive Gandi domain registrar integration for domain and DNS management. Register and manage domains, create/update/delete DNS records (A, AAAA, CNAME, MX, TXT, SRV, and more), configure email forwarding and aliases, check SSL certificate status, create DNS snapshots for safe rollback, bulk update zone files, and monitor domain expiration. Supports multi-domain management, zone file import/export, and automated DNS backups. Includes both read-only and destructive operations with safety controls.