--- name: claude-skill-bash description: Apply comprehensive bash scripting standards including main function pattern, usage documentation, argument parsing, dependency checking, and error handling. Triggers when creating/editing .sh files, bash scripts, or discussing shell scripting, deployment scripts, automation tasks, or bash conventions. --- # Bash Best Practices Skill This skill ensures all bash scripts follow enterprise-grade best practices for maintainability, reliability, and user-friendliness. ## When This Skill Applies This skill should be automatically invoked when: - Creating new bash scripts or shell scripts - Editing existing .sh or .bash files - User requests scripts for automation, deployment, backup, or system tasks - Reviewing or refactoring bash code - User mentions: bash, shell script, main function, argument parsing, script structure ## Script Complexity Guidelines This skill recognizes two categories of scripts with different structural requirements: ### Simple Scripts Scripts that do one simple thing well without unnecessary ceremony. **Characteristics:** - Less than ~30 lines of actual logic (excluding comments and whitespace) - No command-line arguments required - Single, focused purpose that's self-evident - Linear flow or minimal branching - Output is often consumed by other programs or scripts **Examples:** Version detection, environment checks, data format conversion, simple calculations **Structural Requirements:** - Main function and guard clause: **Optional** - Usage function: **Not needed** (no arguments to document) - Argument parsing: **Not applicable** - Color output: **Optional** (often unnecessary) - Can use direct execution without function wrapping ### Ordinary Scripts Scripts with broader scope requiring full structure for maintainability. **Characteristics:** - More than ~30 lines of logic OR - Takes command-line arguments OR - Multiple functions or complex branching OR - Interactive use by humans OR - Requires detailed documentation **Examples:** Deployment tools, backup utilities, system maintenance scripts, development tools **Structural Requirements:** - Main function and guard clause: **Required** - Usage function: **Required** - Argument parsing: **Required** (if taking arguments) - Full error handling: **Required** - User-friendly output: **Required** (for interactive scripts) ## Core Principles to Apply ### For Ordinary Scripts When writing ordinary scripts, ensure: 1. **Main function pattern with guard clause** - Every script must have a main() function 2. **Usage function** - Clear help documentation 3. **Argument parsing** - Structured option handling in main 4. **Dependency checking** - Explicit validation of required tools 5. **Explicit error handling** - Never use `set -e`, handle errors explicitly 6. **Function organization** - Correct ordering and single responsibility 7. **User-friendly output** - Colored, structured output with status indicators ### For Simple Scripts When writing simple scripts, ensure: 1. **Clear purpose comment** - Explain what the script does at the top 2. **Explicit error handling** - Never use `set -e`, handle errors explicitly 3. **Proper exit codes** - 0 for success, non-zero for failure 4. **Error output to stderr** - Use `>&2` for error messages 5. **Variable safety** - Quote variables, handle undefined cases ## Script Structure Templates ### Template for Ordinary Scripts Every new bash script should follow this structure: ```bash #!/usr/bin/env bash # Global declarations DEPENDENCIES=(jq curl git) # List external tools needed SCRIPT_NAME=$(basename "$0") VERSION="1.0.0" # Color definitions (if outputting to terminal) RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Allow users to disable colors if [[ -n "${NO_COLOR:-}" ]] || [[ "${TERM:-}" == "dumb" ]]; then RED="" GREEN="" YELLOW="" BLUE="" NC="" fi function usage() { cat < Input file to process (required) -o|--output Output file (optional, defaults to stdout) -v|--verbose Enable verbose output -h|--help Show this help message --version Show version information dependencies: ${DEPENDENCIES[@]} examples: ${SCRIPT_NAME} -i data.txt -o report.json ${SCRIPT_NAME} --input data.txt --verbose EOM exit 1 } function main() { # Default values local input_file="" local output_file="" local verbose=false # Parse arguments while [ "$1" != "" ]; do case $1 in -i | --input) shift input_file="$1" ;; -o | --output) shift output_file="$1" ;; -v | --verbose) verbose=true ;; --version) echo "${SCRIPT_NAME} version ${VERSION}" exit 0 ;; -h | --help) usage ;; *) echo "Error: Unknown option '$1'" usage ;; esac shift done # Validate required arguments if [ -z "$input_file" ]; then echo "Error: Input file is required" >&2 usage fi # Check dependencies exit_on_missing_tools "${DEPENDENCIES[@]}" # Main script logic process_file "$input_file" "$output_file" "$verbose" } # Business logic functions function process_file() { local input="$1" local output="$2" local verbose="$3" # Implementation here echo "Processing ${input}..." } # Utility functions function exit_on_missing_tools() { for cmd in "$@"; do if command -v "$cmd" &>/dev/null; then continue fi printf "Error: Required tool '%s' is not installed or not in PATH\n" "$cmd" exit 1 done } # Guard clause - only execute main if script is run directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" exit 0 fi ``` ### Template for Simple Scripts For scripts that do one simple thing without arguments: ```bash #!/usr/bin/env bash # Purpose: Determine version for CI builds based on git state # Usage: determine-version.sh # Output: Version string to stdout # For simple scripts, direct execution is fine - no main() required # Check if we're on a tag TAG=$(git describe --exact-match --tags HEAD 2>/dev/null) if [ -n "$TAG" ]; then # On a tag - use the tag as version echo "${TAG#v}" else # Not on tag - determine from branch if [ -n "$GITHUB_HEAD_REF" ]; then # Pull request BRANCH="$GITHUB_HEAD_REF" elif [ -n "$GITHUB_REF_NAME" ]; then # Push to branch BRANCH="$GITHUB_REF_NAME" else # Fallback to current branch BRANCH=$(git symbolic-ref -q --short HEAD 2>/dev/null) if [ -z "$BRANCH" ]; then echo "Error: Unable to determine branch" >&2 exit 1 fi fi # Sanitize branch name for version string SANITIZED=$(echo "$BRANCH" | sed 's/[^a-zA-Z0-9._-]/_/g') echo "${SANITIZED}-SNAPSHOT" fi ``` **Key points for simple scripts:** - Clear purpose comment at the top - Direct execution without main() wrapper - Errors still go to stderr with >&2 - Meaningful exit codes (0 for success, non-zero for failure) - Variables are still properly quoted - No need for usage() function when there are no arguments - No need for color output when output is data ## Detailed Best Practices ### 1. Script Bootstrapping #### Main Function Pattern - **ALWAYS** use a `main` function as the entry point - This enables safe sourcing by other scripts - Provides clear structure and testability #### The Guard Clause ```bash if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" exit 0 fi ``` - Must be at the very end of the script - Enables the script to be sourced without execution - Always include explicit `exit 0` after main ### 2. Usage Function and Argument Parsing #### Usage Function Requirements - Must be defined before main() function - Use heredoc for clean formatting - Include: description, options, dependencies, examples - Exit with status 1 #### Argument Parsing Best Practices ```bash function main() { # Set defaults first local input_file="" local verbose=false # Parse with while loop and case while [ "$1" != "" ]; do case $1 in -i | --input) shift input_file="$1" ;; -v | --verbose) verbose=true ;; -h | --help) usage ;; *) echo "Error: Unknown option '$1'" usage ;; esac shift done # Validate after parsing if [ -z "$input_file" ]; then echo "Error: Input file is required" >&2 usage fi } ``` ### 3. Dependency Declaration and Checking #### Always Declare Dependencies ```bash DEPENDENCIES=(jq curl git docker) ``` #### Check Before Use ```bash function exit_on_missing_tools() { for cmd in "$@"; do if command -v "$cmd" &>/dev/null; then continue fi printf "Error: Required tool '%s' is not installed or not in PATH\n" "$cmd" exit 1 done } ``` #### Include in Usage Show dependencies in the usage function so users know requirements upfront. ### 4. Function Organization #### Correct Function Order (MANDATORY) 1. `usage()` - First after global declarations 2. `main()` - Immediately after usage 3. Business logic functions - Core functionality 4. Utility functions - Generic helpers **NEVER** add section comment headers like "# UTILITY FUNCTIONS". The code structure speaks for itself. #### Function Design Principles - **Single Responsibility**: Each function does one thing - **Clear Inputs/Outputs**: Use local variables, return meaningful status - **Descriptive Names**: Use verb-noun combinations (create_backup, validate_input) - **Keep Small**: Break down functions longer than 50 lines - **Use Local Variables**: Always declare with `local` ### 5. Error Handling #### AVOID `set -e` Never use `set -e`. Instead, use explicit error handling: ```bash # Good - Explicit handling if ! command; then echo "Error: Command failed" >&2 exit 1 fi # Good - Chain with || cd /some/directory || { echo "Error: Cannot change directory" >&2 exit 1 } # Good - Capture and check output=$(command 2>&1) if [ $? -ne 0 ]; then echo "Error: Command failed: $output" >&2 exit 1 fi ``` #### Error Handling Rules - Always redirect errors to stderr with `>&2` - Provide context about what failed - Use meaningful exit codes - Clean up temporary files on failure ### 6. User-Friendly Output #### Color Output (When Appropriate) ```bash # Define colors at script top RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Respect NO_COLOR environment if [[ -n "${NO_COLOR:-}" ]] || [[ "${TERM:-}" == "dumb" ]]; then RED="" GREEN="" YELLOW="" BLUE="" NC="" fi ``` #### Output Functions ```bash function print_header() { echo -e "${BLUE}=======================${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}=======================${NC}" } function print_success() { echo -e "${GREEN}✅ $1${NC}" } function print_error() { echo -e "${RED}❌ Error: $1${NC}" >&2 } function print_warning() { echo -e "${YELLOW}⚠️ Warning: $1${NC}" } function print_step() { local current="$1" local total="$2" local message="$3" echo -e "${YELLOW}[${current}/${total}] ${message}${NC}" } ``` ### 7. Keep Main Function Lean Main should only: 1. Parse arguments 2. Validate inputs 3. Check dependencies 4. Call business functions 5. Handle high-level errors Business logic belongs in separate functions. ### 8. Code Comments #### Avoid Excessive Comments - Well-named functions are self-documenting - Only comment the "why", not the "what" - Use comments for: - Complex algorithms - Non-obvious workarounds - Business logic rules - External API quirks ```bash # Bad - Obvious comment # Increment counter counter=$((counter + 1)) # Good - Explains why # Exponential backoff with jitter to prevent thundering herd delay=$((2 ** attempt * 1000 + RANDOM % 1000)) ``` ## Script Generation Checklist When generating a new script, verify: - [ ] Shebang line: `#!/usr/bin/env bash` - [ ] DEPENDENCIES array declared - [ ] usage() function defined first - [ ] main() function handles argument parsing - [ ] Dependency checking with exit_on_missing_tools - [ ] Guard clause at script end - [ ] Explicit error handling (no `set -e`) - [ ] Functions properly ordered - [ ] Local variables in functions - [ ] Errors redirected to stderr - [ ] Meaningful exit codes - [ ] Color output with NO_COLOR support (if applicable) ## Common Patterns ### Interactive Prompts ```bash # Yes/no confirmation read -p "Continue? (y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Operation cancelled" exit 0 fi # Menu selection echo "Select an option:" echo "1) Full backup" echo "2) Incremental backup" read -p "Enter choice (1-2): " choice # Password (hidden) read -s -p "Enter password: " password echo # Add newline ``` ### Temporary Files ```bash # Create temp file safely temp_file=$(mktemp) trap "rm -f '$temp_file'" EXIT # Use temp directory temp_dir=$(mktemp -d) trap "rm -rf '$temp_dir'" EXIT ``` ### Pipeline Error Handling ```bash # Without set -e, check PIPESTATUS command1 | command2 | command3 if [ "${PIPESTATUS[0]}" -ne 0 ]; then echo "Error: command1 failed" >&2 exit 1 fi ``` ## Examples to Follow When users request scripts for common tasks, apply these patterns: ### Backup Script - Include timestamp in backup names - Verify source exists before backup - Check available disk space - Provide progress feedback - Clean up old backups based on retention ### Deployment Script - Validate environment first - Show clear step progression - Roll back on failure - Log all operations - Provide summary at end ### Data Processing Script - Validate input format - Handle large files efficiently - Provide progress indicators - Support resume on failure - Generate detailed reports ## Choosing Between Simple and Ordinary Scripts ### Decision Tree Ask these questions to determine which pattern to use: 1. **Does the script take command-line arguments?** - YES → Use ordinary script pattern (needs usage() and parsing) - NO → Continue to question 2 2. **Is the logic more than ~30 lines (excluding comments)?** - YES → Use ordinary script pattern (needs structure) - NO → Continue to question 3 3. **Does it have multiple functions or complex branching?** - YES → Use ordinary script pattern (needs organization) - NO → Continue to question 4 4. **Will it be run interactively by humans who need help?** - YES → Use ordinary script pattern (needs user-friendly features) - NO → Continue to question 5 5. **Is it doing one simple, self-evident task?** - YES → Use simple script pattern - NO → Use ordinary script pattern **When in doubt, use the ordinary pattern.** It's better to have structure you don't need than to need structure you don't have. ### Examples of Simple Scripts - `determine-version.sh` - Outputs version string based on git state - `check-env.sh` - Verifies environment variables are set - `format-json.sh` - Reformats JSON from stdin to stdout - `count-lines.sh` - Counts lines in specified file types - `is-production.sh` - Returns 0 if on production branch ### Examples of Ordinary Scripts - `deploy.sh` - Multi-step deployment with rollback - `backup.sh` - Database backup with retention management - `setup-dev.sh` - Developer environment initialization - `test-runner.sh` - Configurable test execution - `migrate-data.sh` - Data migration with validation ## Testing Scripts Always test generated scripts for: 1. **Syntax**: `bash -n script.sh` 2. **Undefined variables**: Run with `set -u` temporarily 3. **Dependencies**: Run without required tools installed 4. **Error paths**: Test with invalid inputs 5. **Sourcing**: Verify guard clause works (for ordinary scripts) ## Remember Every script you generate or modify should be production-ready: - Maintainable by others - Self-documenting through good structure - Gracefully handles errors - Provides helpful output - Follows consistent patterns These practices ensure scripts are professional, reliable, and user-friendly.