Claude Cursor Skill

bash-defensive-patterns

Master defensive Bash programming techniques for production-grade scripts. Use when writing robust shell scripts, CI/CD pipelines, or system utilities requiring fault tolerance and safety.

LLM Mart · 0 points · 14 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download wshobson-agents-plugins_shell-scripting_skills_bash-defensive-patterns-554237f.zip · 4 KB
Part of wshobson/agents — 170 skills

Install

skills CLI npx skills add https://github.com/wshobson/agents/tree/main/plugins/shell-scripting/skills/bash-defensive-patterns
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wshobson-agents@llmmart
Git git clone https://github.com/wshobson/agents.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wshobson/agents collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Bash Defensive Patterns

Comprehensive guidance for writing production-ready Bash scripts using defensive programming techniques, error handling, and safety best practices to prevent common pitfalls and ensure reliability.

When to Use This Skill

  • Writing production automation scripts
  • Building CI/CD pipeline scripts
  • Creating system administration utilities
  • Developing error-resilient deployment automation
  • Writing scripts that must handle edge cases safely
  • Building maintainable shell script libraries
  • Implementing comprehensive logging and monitoring
  • Creating scripts that must work across different platforms

Detailed patterns and worked examples

Detailed pattern documentation lives in references/details.md. Read that file when the navigation tier above is insufficient.

Best Practices Summary

  1. Always use strict mode - set -Eeuo pipefail
  2. Quote all variables - "$variable" prevents word splitting
  3. Use [[]] conditionals - More robust than
  4. Implement error trapping - Catch and handle errors gracefully
  5. Validate all inputs - Check file existence, permissions, formats
  6. Use functions for reusability - Prefix with meaningful names
  7. Implement structured logging - Include timestamps and levels
  8. Support dry-run mode - Allow users to preview changes
  9. Handle temporary files safely - Use mktemp, cleanup with trap
  10. Design for idempotency - Scripts should be safe to rerun
  11. Document requirements - List dependencies and minimum versions
  12. Test error paths - Ensure error handling works correctly
  13. Use command -v - Safer than which for checking executables
  14. Prefer printf over echo - More predictable across systems
Files (agents)
  • references
    • details.md 9.5 KB
      # bash-defensive-patterns — detailed patterns and worked examples
      
      ## Core Defensive Principles
      
      ### 1. Strict Mode
      
      Enable bash strict mode at the start of every script to catch errors early.
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail  # Exit on error, unset variables, pipe failures
      ```
      
      **Key flags:**
      
      - `set -E`: Inherit ERR trap in functions
      - `set -e`: Exit on any error (command returns non-zero)
      - `set -u`: Exit on undefined variable reference
      - `set -o pipefail`: Pipe fails if any command fails (not just last)
      
      ### 2. Error Trapping and Cleanup
      
      Implement proper cleanup on script exit or error.
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      trap 'echo "Error on line $LINENO"' ERR
      trap 'echo "Cleaning up..."; rm -rf "$TMPDIR"' EXIT
      
      TMPDIR=$(mktemp -d)
      # Script code here
      ```
      
      ### 3. Variable Safety
      
      Always quote variables to prevent word splitting and globbing issues.
      
      ```bash
      # Wrong - unsafe
      cp $source $dest
      
      # Correct - safe
      cp "$source" "$dest"
      
      # Required variables - fail with message if unset
      : "${REQUIRED_VAR:?REQUIRED_VAR is not set}"
      ```
      
      ### 4. Array Handling
      
      Use arrays safely for complex data handling.
      
      ```bash
      # Safe array iteration
      declare -a items=("item 1" "item 2" "item 3")
      
      for item in "${items[@]}"; do
          echo "Processing: $item"
      done
      
      # Reading output into array safely
      mapfile -t lines < <(some_command)
      readarray -t numbers < <(seq 1 10)
      ```
      
      ### 5. Conditional Safety
      
      Use `[[ ]]` for Bash-specific features, `[ ]` for POSIX.
      
      ```bash
      # Bash - safer
      if [[ -f "$file" && -r "$file" ]]; then
          content=$(<"$file")
      fi
      
      # POSIX - portable
      if [ -f "$file" ] && [ -r "$file" ]; then
          content=$(cat "$file")
      fi
      
      # Test for existence before operations
      if [[ -z "${VAR:-}" ]]; then
          echo "VAR is not set or is empty"
      fi
      ```
      
      ## Fundamental Patterns
      
      ### Pattern 1: Safe Script Directory Detection
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Correctly determine script directory
      SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
      SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
      
      echo "Script location: $SCRIPT_DIR/$SCRIPT_NAME"
      ```
      
      ### Pattern 2: Comprehensive Function Templat
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Prefix for functions: handle_*, process_*, check_*, validate_*
      # Include documentation and error handling
      
      validate_file() {
          local -r file="$1"
          local -r message="${2:-File not found: $file}"
      
          if [[ ! -f "$file" ]]; then
              echo "ERROR: $message" >&2
              return 1
          fi
          return 0
      }
      
      process_files() {
          local -r input_dir="$1"
          local -r output_dir="$2"
      
          # Validate inputs
          [[ -d "$input_dir" ]] || { echo "ERROR: input_dir not a directory" >&2; return 1; }
      
          # Create output directory if needed
          mkdir -p "$output_dir" || { echo "ERROR: Cannot create output_dir" >&2; return 1; }
      
          # Process files safely
          while IFS= read -r -d '' file; do
              echo "Processing: $file"
              # Do work
          done < <(find "$input_dir" -maxdepth 1 -type f -print0)
      
          return 0
      }
      ```
      
      ### Pattern 3: Safe Temporary File Handling
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      trap 'rm -rf -- "$TMPDIR"' EXIT
      
      # Create temporary directory
      TMPDIR=$(mktemp -d) || { echo "ERROR: Failed to create temp directory" >&2; exit 1; }
      
      # Create temporary files in directory
      TMPFILE1="$TMPDIR/temp1.txt"
      TMPFILE2="$TMPDIR/temp2.txt"
      
      # Use temporary files
      touch "$TMPFILE1" "$TMPFILE2"
      
      echo "Temp files created in: $TMPDIR"
      ```
      
      ### Pattern 4: Robust Argument Parsing
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Default values
      VERBOSE=false
      DRY_RUN=false
      OUTPUT_FILE=""
      THREADS=4
      
      usage() {
          cat <<EOF
      Usage: $0 [OPTIONS]
      
      Options:
          -v, --verbose       Enable verbose output
          -d, --dry-run       Run without making changes
          -o, --output FILE   Output file path
          -j, --jobs NUM      Number of parallel jobs
          -h, --help          Show this help message
      EOF
          exit "${1:-0}"
      }
      
      # Parse arguments
      while [[ $# -gt 0 ]]; do
          case "$1" in
              -v|--verbose)
                  VERBOSE=true
                  shift
                  ;;
              -d|--dry-run)
                  DRY_RUN=true
                  shift
                  ;;
              -o|--output)
                  OUTPUT_FILE="$2"
                  shift 2
                  ;;
              -j|--jobs)
                  THREADS="$2"
                  shift 2
                  ;;
              -h|--help)
                  usage 0
                  ;;
              --)
                  shift
                  break
                  ;;
              *)
                  echo "ERROR: Unknown option: $1" >&2
                  usage 1
                  ;;
          esac
      done
      
      # Validate required arguments
      [[ -n "$OUTPUT_FILE" ]] || { echo "ERROR: -o/--output is required" >&2; usage 1; }
      ```
      
      ### Pattern 5: Structured Logging
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Logging functions
      log_info() {
          echo "[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $*" >&2
      }
      
      log_warn() {
          echo "[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $*" >&2
      }
      
      log_error() {
          echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
      }
      
      log_debug() {
          if [[ "${DEBUG:-0}" == "1" ]]; then
              echo "[$(date +'%Y-%m-%d %H:%M:%S')] DEBUG: $*" >&2
          fi
      }
      
      # Usage
      log_info "Starting script"
      log_debug "Debug information"
      log_warn "Warning message"
      log_error "Error occurred"
      ```
      
      ### Pattern 6: Process Orchestration with Signals
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Track background processes
      PIDS=()
      
      cleanup() {
          log_info "Shutting down..."
      
          # Terminate all background processes
          for pid in "${PIDS[@]}"; do
              if kill -0 "$pid" 2>/dev/null; then
                  kill -TERM "$pid" 2>/dev/null || true
              fi
          done
      
          # Wait for graceful shutdown
          for pid in "${PIDS[@]}"; do
              wait "$pid" 2>/dev/null || true
          done
      }
      
      trap cleanup SIGTERM SIGINT
      
      # Start background tasks
      background_task &
      PIDS+=($!)
      
      another_task &
      PIDS+=($!)
      
      # Wait for all background processes
      wait
      ```
      
      ### Pattern 7: Safe File Operations
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Use -i flag to move safely without overwriting
      safe_move() {
          local -r source="$1"
          local -r dest="$2"
      
          if [[ ! -e "$source" ]]; then
              echo "ERROR: Source does not exist: $source" >&2
              return 1
          fi
      
          if [[ -e "$dest" ]]; then
              echo "ERROR: Destination already exists: $dest" >&2
              return 1
          fi
      
          mv "$source" "$dest"
      }
      
      # Safe directory cleanup
      safe_rmdir() {
          local -r dir="$1"
      
          if [[ ! -d "$dir" ]]; then
              echo "ERROR: Not a directory: $dir" >&2
              return 1
          fi
      
          # Use -I flag to prompt before rm (BSD/GNU compatible)
          rm -rI -- "$dir"
      }
      
      # Atomic file writes
      atomic_write() {
          local -r target="$1"
          local -r tmpfile
          tmpfile=$(mktemp) || return 1
      
          # Write to temp file first
          cat > "$tmpfile"
      
          # Atomic rename
          mv "$tmpfile" "$target"
      }
      ```
      
      ### Pattern 8: Idempotent Script Design
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Check if resource already exists
      ensure_directory() {
          local -r dir="$1"
      
          if [[ -d "$dir" ]]; then
              log_info "Directory already exists: $dir"
              return 0
          fi
      
          mkdir -p "$dir" || {
              log_error "Failed to create directory: $dir"
              return 1
          }
      
          log_info "Created directory: $dir"
      }
      
      # Ensure configuration state
      ensure_config() {
          local -r config_file="$1"
          local -r default_value="$2"
      
          if [[ ! -f "$config_file" ]]; then
              echo "$default_value" > "$config_file"
              log_info "Created config: $config_file"
          fi
      }
      
      # Rerunning script multiple times should be safe
      ensure_directory "/var/cache/myapp"
      ensure_config "/etc/myapp/config" "DEBUG=false"
      ```
      
      ### Pattern 9: Safe Command Substitution
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      # Use $() instead of backticks
      name=$(<"$file")  # Modern, safe variable assignment from file
      output=$(command -v python3)  # Get command location safely
      
      # Handle command substitution with error checking
      result=$(command -v node) || {
          log_error "node command not found"
          return 1
      }
      
      # For multiple lines
      mapfile -t lines < <(grep "pattern" "$file")
      
      # NUL-safe iteration
      while IFS= read -r -d '' file; do
          echo "Processing: $file"
      done < <(find /path -type f -print0)
      ```
      
      ### Pattern 10: Dry-Run Support
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      DRY_RUN="${DRY_RUN:-false}"
      
      run_cmd() {
          if [[ "$DRY_RUN" == "true" ]]; then
              echo "[DRY RUN] Would execute: $*"
              return 0
          fi
      
          "$@"
      }
      
      # Usage
      run_cmd cp "$source" "$dest"
      run_cmd rm "$file"
      run_cmd chown "$owner" "$target"
      ```
      
      ## Advanced Defensive Techniques
      
      ### Named Parameters Pattern
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      process_data() {
          local input_file=""
          local output_dir=""
          local format="json"
      
          # Parse named parameters
          while [[ $# -gt 0 ]]; do
              case "$1" in
                  --input=*)
                      input_file="${1#*=}"
                      ;;
                  --output=*)
                      output_dir="${1#*=}"
                      ;;
                  --format=*)
                      format="${1#*=}"
                      ;;
                  *)
                      echo "ERROR: Unknown parameter: $1" >&2
                      return 1
                      ;;
              esac
              shift
          done
      
          # Validate required parameters
          [[ -n "$input_file" ]] || { echo "ERROR: --input is required" >&2; return 1; }
          [[ -n "$output_dir" ]] || { echo "ERROR: --output is required" >&2; return 1; }
      }
      ```
      
      ### Dependency Checking
      
      ```bash
      #!/bin/bash
      set -Eeuo pipefail
      
      check_dependencies() {
          local -a missing_deps=()
          local -a required=("jq" "curl" "git")
      
          for cmd in "${required[@]}"; do
              if ! command -v "$cmd" &>/dev/null; then
                  missing_deps+=("$cmd")
              fi
          done
      
          if [[ ${#missing_deps[@]} -gt 0 ]]; then
              echo "ERROR: Missing required commands: ${missing_deps[*]}" >&2
              return 1
          fi
      }
      
      check_dependencies
      ```
      
  • SKILL.md 1.9 KB
    ---
    name: bash-defensive-patterns
    description: Master defensive Bash programming techniques for production-grade scripts. Use when writing robust shell scripts, CI/CD pipelines, or system utilities requiring fault tolerance and safety.
    ---
    
    # Bash Defensive Patterns
    
    Comprehensive guidance for writing production-ready Bash scripts using defensive programming techniques, error handling, and safety best practices to prevent common pitfalls and ensure reliability.
    
    ## When to Use This Skill
    
    - Writing production automation scripts
    - Building CI/CD pipeline scripts
    - Creating system administration utilities
    - Developing error-resilient deployment automation
    - Writing scripts that must handle edge cases safely
    - Building maintainable shell script libraries
    - Implementing comprehensive logging and monitoring
    - Creating scripts that must work across different platforms
    
    ## Detailed patterns and worked examples
    
    Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient.
    
    ## Best Practices Summary
    
    1. **Always use strict mode** - `set -Eeuo pipefail`
    2. **Quote all variables** - `"$variable"` prevents word splitting
    3. **Use [[]] conditionals** - More robust than [ ]
    4. **Implement error trapping** - Catch and handle errors gracefully
    5. **Validate all inputs** - Check file existence, permissions, formats
    6. **Use functions for reusability** - Prefix with meaningful names
    7. **Implement structured logging** - Include timestamps and levels
    8. **Support dry-run mode** - Allow users to preview changes
    9. **Handle temporary files safely** - Use mktemp, cleanup with trap
    10. **Design for idempotency** - Scripts should be safe to rerun
    11. **Document requirements** - List dependencies and minimum versions
    12. **Test error paths** - Ensure error handling works correctly
    13. **Use `command -v`** - Safer than `which` for checking executables
    14. **Prefer printf over echo** - More predictable across systems
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related