scripting-automation
Skill17 starsChanged 55 days agoMITView on GitHub
Advanced Bash automation patterns for production-grade scripts and automation workflows. Covers retry logic, lock files, signal handling, advanced error handling, performance optimization, testing with BATS, and cross-platform compatibility. Use when building automation scripts, deployment tools, CI/CD helpers, or when asking about advanced Bash patterns, script reliability, or automation best practices.
- Deletes or force-pushes
In this repository53 other files it gives its agents.
- agents-sdkSkill
- agent-workflowSkill
- api-designSkill
- aws-iamSkill
- bash-shell-scriptingSkill
- cicd-github-actionsSkill
- cloudflare-email-serviceSkill
- cloudflareSkill
- cloudflare-waf-authorSkill
- cloudflare-workers-authorSkill
- cloud-platformsSkill
- codebase-security-auditSkill
- containers-orchestrationSkill
- core-engineeringSkill
- database-postgresqlSkill
- databricksSkill
- data-engineeringSkill
- distributed-transactionsSkill
- documentation-standardsSkill
- domain-driven-designSkill
- durable-objectsSkill
- frontend-engineeringSkill
- git-workflowSkill
- go-rust-systemsSkill
- iam-security-advisorSkill
- independent-verificationSkill
- infrastructure-iacSkill
- kubernetes-containersSkill
- kubernetes-operator-developmentSkill
- mcp-developmentSkill
- memory-architectureSkill
- multi-perspective-reviewSkill
- networking-transportSkill
- observabilitySkill
- oktaSkill
- pdf-exportSkill
- python-developmentSkill
- reactflow-architecture-diagramsSkill
- sandbox-sdkSkill
- security-testingSkill
- service-resilienceSkill
- single-file-dashboardSkill
- skills-compositionSkill
- skills-continuous-improvementSkill
- snowflakeSkill
- system-designSkill
- typescript-javascriptSkill
- web-perfSkill
- web-research-kb-refreshSkill
- workers-best-practicesSkill
- workload-identitySkill
- wranglerSkill
- zero-trustSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. About 0 are common, 7 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use the Bash skill (${HANDBOOK_ROOT}/skills/bash-shell-scripting/SKILL.md) for baseline shell authoring.
- Distinctive: line 12
"Fail fast, fail clearly": - Use strict mode, validate early, provide clear error messages
- Distinctive: line 13
"Quotes are your friend": - Always quote variables unless you explicitly want word splitting
- Very specific: line 14
"Explicit over implicit": - Use local, readonly, clear function names, document assumptions
- Distinctive: line 18
"Test what you write": - Use shellcheck, test on multiple platforms, write BATS tests
- Very specific: line 19
"Format consistently": - Must pass shfmt -i 2 -ci -sr -bn (2-space indentation.
- Very specific: line 40
Use this for new automation scripts.
- Distinctive: line 113
Use for: Simple linear scripts, dependency installation, straightforward validation.
- Distinctive: line 121
Use for: Diagnostics, cleanup operations, commands where failure is expected (grep, curl with retries).
- Distinctive: line 135
Use for: Production scripts with comprehensive error handling via ERR traps.
- Very specific: line 141
Use main() once an automation script has multiple named phases.
- Very specific: line 160
Keep truly tiny scripts linear.
- Distinctive: line 321
Only emit colors for interactive terminal output.
- Very specific: line 497
Test scripts with dash or sh for POSIX compliance
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Scripting & Automation
Use the Bash skill (${HANDBOOK_ROOT}/skills/bash-shell-scripting/SKILL.md) for baseline shell authoring. This skill owns automation-specific retries, locks, signals, idempotency, deployment workflows, and BATS patterns.
Guiding Principles
- "Fail fast, fail clearly" - Use strict mode, validate early, provide clear error messages
- "Quotes are your friend" - Always quote variables unless you explicitly want word splitting
- "Explicit over implicit" - Use
local,readonly, clear function names, document assumptions - "Security by default" - Sanitize inputs, use
mktemp, avoideval, validate file paths - "Composition over complexity" - Small functions, clear separation of concerns, reusable patterns
- "Observability is essential" - Structured logging, proper exit codes, error context
- "Test what you write" - Use shellcheck, test on multiple platforms, write BATS tests
- "Format consistently" - Must pass
shfmt -i 2 -ci -sr -bn(2-space indentation; prefer ~100-character lines)
Quick Reference
# Safety modes
set -euo pipefail # Strict mode (fail-fast)
set -uo pipefail # Controlled mode (explicit error handling)
set -Euo pipefail # Strict + ERR trap propagation to functions
# Common patterns
command -v cmd >/dev/null # Check if command exists (portable)
trap 'cleanup' EXIT # Always cleanup
flock -n 200 || exit 1 # Prevent concurrent runs
readonly VAR="value" # Immutable constant
local var="value" # Function-local variable
shfmt -w -i 2 -ci -sr -bn . # Format with Google-style 2-space indentationStandard Header + Prelude
Use this for new automation scripts. It documents purpose and usage, keeps debug tracing available but commented, creates a timestamped logfile named after the script, and uses a safe logging helper instead of raw echo.
#!/usr/bin/env bash
#
# Script Name : <script_name>.sh
#
# Purpose : <One or two sentences explaining what the script does.
# Wrap continuation lines under the value column.>
#
# Dependencies : <List required commands, or "None">
#
# Script Usage : ./<script_name>.sh [options] <arguments>
#
# <Examples and argument notes.>
#
##----------------------------------------------------------------------------------------##
# Turn debug on or off
# set -x
##----------------------------------------------------------------------------------------##
set -euo pipefail
readonly DTTM="$(date -u +"%Y%m%d_%H%M%S")"
readonly SCRIPT_NAME="$(basename "${0}" .sh)"
readonly LOGFILE="${SCRIPT_NAME}_${DTTM}.log"
logmsg() {
local timestamp
timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
printf "%s: %s\n" "${timestamp}" "$*" | tee -a "${LOGFILE}" >&2
}
die() {
logmsg "ERROR: $*"
exit 1
}
debug() {
[[ "${DEBUG:-0}" == "1" ]] && logmsg "DEBUG: $*"
}
require_command() {
local command_name="$1"
command -v "${command_name}" >/dev/null 2>&1 || die "Missing command: ${command_name}"
}Generation Contract
For any non-trivial automation script (argument parsing, dependency checks, config resolution, build/deploy/verify steps), generate this structure:
- Header block.
- Commented debug toggle (
# set -x), disabled by default. - Strict or controlled mode.
- Readonly constants and timestamped
LOGFILE. logmsg,die, and optionaldebughelper.require_commandfor dependencies.- Helper functions with lowercase locals.
parse_args.main().main "$@"as the final line.
Reject generated automation scripts that skip this structure unless the script is intentionally tiny and linear.
Error Handling Modes
Strict Mode (Fail-Fast)
set -euo pipefail # Exit immediately on any errorUse for: Simple linear scripts, dependency installation, straightforward validation.
Controlled Mode (Explicit)
set -uo pipefail # No -e: you handle errors explicitlyUse for: Diagnostics, cleanup operations, commands where failure is expected (grep, curl with retries).
Strict + ERR Trap Propagation
set -Euo pipefail # -E ensures ERR traps propagate to functions
trap 'echo "ERROR in ${FUNCNAME[0]:-main} at line $LINENO"' ERR
func1() {
false # Will trigger ERR trap even inside function
}Use for: Production scripts with comprehensive error handling via ERR traps.
Advanced Patterns
Main Function for Multi-Step Automation
Use main() once an automation script has multiple named phases. This keeps constants and function definitions separate from execution, makes the script easy to scan, and gives tests a natural set of functions to call.
main() {
parse_args "$@"
require_command curl
require_command git
require_command npm
resolve_env_file
install_dependencies_if_needed
build_assets
load_deploy_environment
deploy_worker_assets "$@"
verify_worker_hostnames
}
main "$@"Keep truly tiny scripts linear. If you are adding phase comments like "parse", "validate", "deploy", or "verify", promote those phases into functions and call them from main().
Know When Bash Is the Wrong Tool
Use Bash for glue: calling CLIs, moving files, simple validation, CI wrappers, and deployment orchestration. Prefer Python/Go/Node when the script needs complex data structures, non-trivial JSON transformation, API clients with pagination/retry state, concurrency, long-lived daemons, or more than a few hundred lines of business logic.
Retry with Exponential Backoff
retry_with_backoff() {
local max_attempts="${1:-5}"
local delay="${2:-1}"
shift 2
local attempt=1
while ! "$@"; do
if ((attempt >= max_attempts)); then
echo "Failed after $max_attempts attempts" >&2
return 1
fi
echo "Attempt $attempt/$max_attempts failed, retrying in ${delay}s..." >&2
sleep "$delay"
delay=$((delay * 2 > 60 ? 60 : delay * 2)) # Cap at 60s
((attempt++))
done
}
# Usage
retry_with_backoff 5 2 curl -f https://api.acme.com/healthLock File Pattern
acquire_lock() {
local lock_file="${1:-/tmp/script.lock}"
local lock_fd=200
exec ${lock_fd}>"$lock_file" || {
echo "Failed to open lock file" >&2
return 1
}
if ! flock -n "$lock_fd"; then
echo "Another instance is running" >&2
return 1
fi
echo "Lock acquired"
trap "flock -u $lock_fd; rm -f $lock_file" EXIT
}Signal Handling
cleanup() {
echo "Cleaning up..." >&2
rm -f "$TEMP_FILE"
# Release resources, close connections, etc.
}
trap cleanup EXIT INT TERM
# Handle specific signals
trap 'echo "Received SIGUSR1"' USR1
trap 'echo "Received SIGUSR2"' USR2Performance Optimization
Avoid Subshells (Process Substitution)
# ❌ Wrong - subshell, variables don't persist
count=0
cat file.txt | while read -r line; do
count=$((count + 1))
done
echo "$count" # Empty! (subshell issue)
# ✅ Correct - process substitution, no subshell
count=0
while read -r line; do
count=$((count + 1))
done < file.txt
echo "$count" # Works! Variable persistsPrefer Builtins Over External Commands
# ❌ BAD: External command (slow)
result=$(echo "$var" | sed 's/old/new/g')
# ✅ GOOD: Builtin parameter expansion (fast)
result="${var//old/new}"
# ❌ BAD: External command
length=$(echo "$var" | wc -c)
# ✅ GOOD: Builtin expansion
length=${#var}Use awk, sed, and jq Deliberately
# Simple string changes: prefer Bash parameter expansion
normalized_path="${input_path//\/\//\/}"
# Use awk/sed only when the operation is truly line-, column-, or regex-oriented
awk -F',' 'NR > 1 && $3 == "active" { print $1 }' users.csv
sed -E 's/[[:space:]]+$//' input.txt > output.txt
# JSON must use jq
jq -e '.items[] | select(.enabled == true) | .id' config.jsonUse Arrays for Collections
# ❌ BAD: String concatenation (slow, error-prone)
files=""
for file in *.txt; do
files="$files $file"
done
# ✅ GOOD: Arrays (fast, safe)
files=()
for file in *.txt; do
[[ -f "$file" ]] || continue
files+=("$file")
done
# Process array
for file in "${files[@]}"; do
process "$file"
doneLogging & Observability
Structured Logging
log() {
local level="$1"
shift
# UTC ISO-8601 timestamp
printf "%s [%s] %s\n" "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$level" "$*" >&2
}
log_info() { log "INFO" "$@"; }
log_error() { log "ERROR" "$@"; }
log_debug() { [[ "${DEBUG:-0}" == "1" ]] && log "DEBUG" "$@" || true; }Terminal-safe ANSI colors
Only emit colors for interactive terminal output. Do not write ANSI escape codes to log files.
if [[ -t 2 ]]; then
readonly RED=$'\033[0;31m'
readonly GREEN=$'\033[0;32m'
readonly NC=$'\033[0m'
else
readonly RED=''
readonly GREEN=''
readonly NC=''
fi
if [[ -n "${RED}" ]]; then
printf "%sInvalid input%s\n" "${RED}" "${NC}" >&2
fi
log_error "Invalid input"Structured Error Reporting (JSON)
log_error_json() {
local message="$*"
local timestamp
timestamp=$(date -u +"%Y-%m-%d %T")
printf '{"timestamp":"%s","level":"ERROR","message":"%s","source":"%s","line":%d,"function":"%s"}\n' \
"$timestamp" "$message" "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${FUNCNAME[1]:-main}" \
>> "${LOG_FILE}.errors"
}Testing with BATS
BATS Test Structure
#!/usr/bin/env bats
load test_helper
@test "function returns success on valid input" {
run my_function "valid_input"
[ "$status" -eq 0 ]
[ "$output" = "expected_output" ]
}
@test "function fails on invalid input" {
run my_function ""
[ "$status" -eq 1 ]
[[ "$output" =~ "Error" ]]
}
@test "function is idempotent" {
run my_function "input"
first_output="$output"
run my_function "input"
[ "$output" = "$first_output" ]
}Test Helpers
# test_helper.bash
setup() {
TEST_DIR=$(mktemp -d)
cd "$TEST_DIR" || exit 1
}
teardown() {
rm -rf "$TEST_DIR"
}
# Mock external commands
mock_command() {
local cmd="$1"
local output="$2"
echo "$output" > "$TEST_DIR/$cmd"
chmod +x "$TEST_DIR/$cmd"
PATH="$TEST_DIR:$PATH"
}Security Best Practices
Sanitize Inputs
# Use read -r (no backslash escaping)
read -r user_input
# Validate input
case "$user_input" in
[0-9]*) echo "Valid number" ;;
*) echo "Invalid input"; exit 1 ;;
esacSecure Temporary Files
umask 077
TEMP=$(mktemp) || { echo "Failed to create temp file"; exit 1; }
trap 'rm -f "$TEMP"' EXIT
# Write sensitive data
echo "secret" > "$TEMP"Avoid eval with User Input
# ❌ NEVER DO THIS
eval "$user_input"
# ✅ Use case statements or arrays instead
case "$user_input" in
start) start_service ;;
stop) stop_service ;;
*) echo "Invalid command"; exit 1 ;;
esacPrompting & User Interaction
Yes/No/Cancel Prompt
prompt_ync() {
local yn
while true; do
read -n 1 -p "$1 [y/n/c] " yn
echo
case "$yn" in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
[Cc]*) exit 2 ;;
*) echo "Invalid input (y/n/c)" ;;
esac
done
}Secure Password Prompt
# Prompt for password without echoing
read -r -s -p "Enter password: " password
echo # Newline after hidden input
# Validate password strength
if [[ ${#password} -lt 8 ]]; then
echo "Password must be at least 8 characters" >&2
exit 1
fiCross-Platform Compatibility
GNU vs BSD Tools
# GNU (Linux)
SCRIPT=$(readlink -f "$0")
# BSD/macOS fallback
SCRIPT=$(readlink -f "$0" 2>/dev/null || realpath "$0")Version Compatibility
- macOS ships with Bash 3.2 - Install Bash 4+ via Homebrew for modern features
- For maximum portability (POSIX sh), avoid
[[,${var,,}, associative arrays - Test scripts with
dashorshfor POSIX compliance
Common Pitfalls to Avoid
Unquoted Variables
# ❌ Wrong
if [ $var = "hello" ]; then # Fails if var is empty or has spaces
# ✅ Correct
if [ "$var" = "hello" ]; then
# Or use [[ (Bash-specific but safer)
if [[ $var = "hello" ]]; thenParsing ls Output
# ❌ Wrong
for file in $(ls *.txt); do
# ✅ Correct
for file in *.txt; do
[ -f "$file" ] || continuecd Without Error Checking
# ❌ Wrong
cd "$some_dir"
rm -rf * # Disaster if cd failed!
# ✅ Correct
cd "$some_dir" || { echo "Failed to cd"; exit 1; }
rm -rf *Using $* Instead of "$@"
# ❌ WRONG - $* loses argument boundaries
my_script() {
for arg in $*; do
echo "$arg"
done
}
# ✅ CORRECT - "$@" preserves boundaries
my_script() {
for arg in "$@"; do
echo "$arg"
done
}Automation Script Checklist
- Strict error handling (
set -euo pipefailorset -uo pipefail) - Lock file for concurrency control
- Dependency checking at startup
- Input validation
- Structured logging (UTC timestamps, levels)
- Retry logic for network calls
- Cleanup on exit (trap EXIT)
- Usage documentation
- Exit codes documented
- Passes shellcheck with 0 warnings
- Formatted with shfmt
- Tested with BATS
Detailed References
- Bash authoring: See the Bash skill (
${HANDBOOK_ROOT}/skills/bash-shell-scripting/SKILL.md)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

