| name | bash |
| description | Guide for writing production-quality bash scripts following modern idiomatic practices. Enforces set -euo pipefail, [[ ]] conditionals, ${var} syntax, ShellCheck compliance. Triggers on "bash script", "shell script", ".sh file", "write a script", "automation script", "bash function", "shellcheck", "bash template", "pre-commit hook", "deploy script", "build script", "install script", "setup script", "bash error handling", "bash arrays", "bash loop", "bash conditional", "parse arguments", "getopts", "bash logging", "#!/bin/bash", "source script", "dot script", "shell function", "edit script", "update script", "modify script", "change script", "edit .sh", "update .sh", "modify .sh", "statusline.sh", "hook script". PROACTIVE: MUST invoke BEFORE editing/writing ANY .sh file or pre-commit hook. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
ABOUTME: Bash scripting skill for production-quality scripts with Bash 4.x+
ABOUTME: Emphasizes safety, readability, maintainability, and mandatory ShellCheck compliance
Bash Scripting Skill
Target: Bash 4.0+ with mandatory ShellCheck compliance.
Detailed patterns: See references/script-template.md and references/advanced-patterns.md
🚨 PROACTIVE INVOCATION REQUIRED
┌─────────────────────────────────────────────────────────────┐
│ BEFORE using Edit/Write on ANY .sh file: │
│ │
│ 1. CHECK the file extension - is it .sh? │
│ 2. If YES → This skill MUST be active │
│ 3. If skill not loaded → STOP and invoke /bash first │
│ │
│ This applies even if the user didn't mention "bash" or │
│ "script" - file extension is the trigger! │
│ │
│ Examples that require /bash skill: │
│ - "update the statusline" (edits statusline.sh) │
│ - "add a feature to the hook" (edits pre-commit) │
│ - "fix the deploy script" (edits deploy.sh) │
└─────────────────────────────────────────────────────────────┘
Quick Reference
| Pattern | Use | Avoid |
|---|---|---|
| Conditionals | [[ "${var}" == "x" ]] |
[ $var == "x" ] |
| Variables | "${var}" (quoted) |
$var (unquoted) |
| Command sub | $(command) |
`command` |
| Arithmetic | (( count++ )) |
let count++ |
| Error handling | set -euo pipefail |
No safety flags |
When to Use Bash
USE for (< 200 lines):
- Quick automation tasks
- Build/deployment scripts
- System administration
- Glue code between tools
DON'T USE for:
- Complex business logic
- Robust error handling with recovery
- Long-running services
- Code requiring unit testing
If script exceeds ~200 lines, consider Python or Go.
🔄 RESUMED SESSION CHECKPOINT
┌─────────────────────────────────────────────────────────────┐
│ SESSION RESUMED - BASH SKILL VERIFICATION │
│ │
│ Before continuing: │
│ 1. Does script have set -euo pipefail? │
│ 2. Are all variables quoted: "${var}"? │
│ 3. Using [[ ]] conditionals (not [ ])? │
│ 4. Run: shellcheck <script>.sh │
└─────────────────────────────────────────────────────────────┘
Mandatory Header
Every script MUST start with:
#!/bin/bash
set -euo pipefail
# -e: Exit on error
# -u: Error on unset variables
# -o pipefail: Pipeline fails on first error
Core Syntax Rules
Conditionals (Always [[ ]])
if [[ "${var}" =~ ^[0-9]+$ ]]; then
echo "Numeric"
fi
if [[ -f "${file}" && -r "${file}" ]]; then
echo "File exists and readable"
fi
Variable Expansion (Always quoted)
echo "Value: ${config_path}"
echo "Array: ${my_array[@]}"
Command Substitution
current_date=$(date +"%Y-%m-%d")
file_count=$(find . -type f | wc -l)
Arithmetic
(( count++ ))
(( total = value1 + value2 ))
if (( count > 10 )); then
echo "Exceeded"
fi
Error Handling Patterns
Pattern 1: Need output AND status
output=$(complex_command 2>&1)
rc=$?
if [[ ${rc} -ne 0 ]]; then
log_error "Failed: ${output}"
return 1
fi
Pattern 2: Status check only
if ! simple_command --flag; then
die "Command failed"
fi
# Or short form
simple_command || die "Failed"
Essential Functions
# Logging
log_info() { echo "[INFO] $(date +%H:%M:%S) ${*}" >&2; }
log_error() { echo "[ERROR] $(date +%H:%M:%S) ${*}" >&2; }
die() { log_error "${*}"; exit 1; }
# Validation
validate_file() {
local -r f="${1}"
[[ -f "${f}" ]] || die "Not found: ${f}"
[[ -r "${f}" ]] || die "Not readable: ${f}"
}
# Cleanup
cleanup() {
[[ -d "${TEMP_DIR:-}" ]] && rm -rf "${TEMP_DIR}"
}
trap cleanup EXIT
Argument Parsing
parse_arguments() {
while [[ $# -gt 0 ]]; do
case "${1}" in
-h|--help) help; exit 0 ;;
-v|--verbose) VERBOSE=true; shift ;;
-f|--file)
[[ -z "${2:-}" ]] && die "-f requires argument"
FILE="${2}"; shift 2
;;
-*) die "Unknown: ${1}" ;;
*) break ;;
esac
done
ARGS=("${@}")
}
ShellCheck Compliance
All scripts MUST pass ShellCheck with zero warnings.
Common Fixes
| Warning | Fix |
|---|---|
| SC2086: Quote to prevent globbing | rm "${file}" not rm $file |
| SC2155: Declare separately | local x; x=$(cmd) not local x=$(cmd) |
| SC1090: Can't follow source | # shellcheck source=/dev/null |
| SC2046: Quote command sub | Use while read instead of for x in $(cmd) |
Run ShellCheck
shellcheck script.sh
shellcheck -s bash script.sh
find . -name "*.sh" -exec shellcheck {} +
Arrays Quick Reference
Indexed Arrays
declare -a files=()
files+=("item")
for f in "${files[@]}"; do echo "${f}"; done
echo "Count: ${#files[@]}"
Associative Arrays (Bash 4+)
declare -A config=()
config["key"]="value"
for k in "${!config[@]}"; do echo "${k}=${config[${k}]}"; done
Security Rules
- Never use
eval - Sanitize all inputs
- Use absolute paths for system commands
- Use
readonlyfor constants - Don't store secrets in scripts
Checklist
Before script is complete:
-
set -euo pipefailat top - All variables use
"${var}"syntax - Uses
[[ ]]for conditionals - Uses
$(command)for substitution - ShellCheck passes (zero warnings)
- Has
help()function - Has cleanup trap
- Variables properly scoped (local/readonly)
- If > 100 lines: Has bats tests
Full Template
See references/script-template.md for complete production script template.
Advanced Patterns
See references/advanced-patterns.md for:
- Arrays and associative arrays
- String manipulation
- Parallel processing
- Progress indicators
- Input validation
- bats-core testing