Bash Defensive Patterns
Write bulletproof Bash scripts with production-grade defensive patterns
✨ The solution you've been looking for
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.
See It In Action
Interactive preview & real-world examples
AI Conversation Simulator
See how users interact with this skill
User Prompt
Help me write a deployment script that safely copies files, validates checksums, and rolls back on failure. It should handle edge cases and provide detailed logging.
Skill Processing
Analyzing request...
Agent Response
A production-ready script with strict mode, error trapping, cleanup handlers, and structured logging that prevents common pitfalls
Quick Start (3 Steps)
Get up and running in minutes
Install
claude-code skill install bash-defensive-patterns
claude-code skill install bash-defensive-patternsConfig
First Trigger
@bash-defensive-patterns helpCommands
| Command | Description | Required Args |
|---|---|---|
| @bash-defensive-patterns production-ci/cd-pipeline-script | Create a robust deployment script that handles failures gracefully and provides comprehensive logging | None |
| @bash-defensive-patterns system-administration-utility | Build a maintainable script for routine system maintenance that can be safely rerun | None |
| @bash-defensive-patterns data-processing-automation | Develop a fault-tolerant script for processing large datasets with parallel execution | None |
Typical Use Cases
Production CI/CD Pipeline Script
Create a robust deployment script that handles failures gracefully and provides comprehensive logging
System Administration Utility
Build a maintainable script for routine system maintenance that can be safely rerun
Data Processing Automation
Develop a fault-tolerant script for processing large datasets with parallel execution
Overview
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
Core Defensive Principles
1. Strict Mode
Enable bash strict mode at the start of every script to catch errors early.
1#!/bin/bash
2set -Eeuo pipefail # Exit on error, unset variables, pipe failures
Key flags:
set -E: Inherit ERR trap in functionsset -e: Exit on any error (command returns non-zero)set -u: Exit on undefined variable referenceset -o pipefail: Pipe fails if any command fails (not just last)
2. Error Trapping and Cleanup
Implement proper cleanup on script exit or error.
1#!/bin/bash
2set -Eeuo pipefail
3
4trap 'echo "Error on line $LINENO"' ERR
5trap 'echo "Cleaning up..."; rm -rf "$TMPDIR"' EXIT
6
7TMPDIR=$(mktemp -d)
8# Script code here
3. Variable Safety
Always quote variables to prevent word splitting and globbing issues.
1# Wrong - unsafe
2cp $source $dest
3
4# Correct - safe
5cp "$source" "$dest"
6
7# Required variables - fail with message if unset
8: "${REQUIRED_VAR:?REQUIRED_VAR is not set}"
4. Array Handling
Use arrays safely for complex data handling.
1# Safe array iteration
2declare -a items=("item 1" "item 2" "item 3")
3
4for item in "${items[@]}"; do
5 echo "Processing: $item"
6done
7
8# Reading output into array safely
9mapfile -t lines < <(some_command)
10readarray -t numbers < <(seq 1 10)
5. Conditional Safety
Use [[ ]] for Bash-specific features, [ ] for POSIX.
1# Bash - safer
2if [[ -f "$file" && -r "$file" ]]; then
3 content=$(<"$file")
4fi
5
6# POSIX - portable
7if [ -f "$file" ] && [ -r "$file" ]; then
8 content=$(cat "$file")
9fi
10
11# Test for existence before operations
12if [[ -z "${VAR:-}" ]]; then
13 echo "VAR is not set or is empty"
14fi
Fundamental Patterns
Pattern 1: Safe Script Directory Detection
1#!/bin/bash
2set -Eeuo pipefail
3
4# Correctly determine script directory
5SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
6SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
7
8echo "Script location: $SCRIPT_DIR/$SCRIPT_NAME"
Pattern 2: Comprehensive Function Templat
1#!/bin/bash
2set -Eeuo pipefail
3
4# Prefix for functions: handle_*, process_*, check_*, validate_*
5# Include documentation and error handling
6
7validate_file() {
8 local -r file="$1"
9 local -r message="${2:-File not found: $file}"
10
11 if [[ ! -f "$file" ]]; then
12 echo "ERROR: $message" >&2
13 return 1
14 fi
15 return 0
16}
17
18process_files() {
19 local -r input_dir="$1"
20 local -r output_dir="$2"
21
22 # Validate inputs
23 [[ -d "$input_dir" ]] || { echo "ERROR: input_dir not a directory" >&2; return 1; }
24
25 # Create output directory if needed
26 mkdir -p "$output_dir" || { echo "ERROR: Cannot create output_dir" >&2; return 1; }
27
28 # Process files safely
29 while IFS= read -r -d '' file; do
30 echo "Processing: $file"
31 # Do work
32 done < <(find "$input_dir" -maxdepth 1 -type f -print0)
33
34 return 0
35}
Pattern 3: Safe Temporary File Handling
1#!/bin/bash
2set -Eeuo pipefail
3
4trap 'rm -rf -- "$TMPDIR"' EXIT
5
6# Create temporary directory
7TMPDIR=$(mktemp -d) || { echo "ERROR: Failed to create temp directory" >&2; exit 1; }
8
9# Create temporary files in directory
10TMPFILE1="$TMPDIR/temp1.txt"
11TMPFILE2="$TMPDIR/temp2.txt"
12
13# Use temporary files
14touch "$TMPFILE1" "$TMPFILE2"
15
16echo "Temp files created in: $TMPDIR"
Pattern 4: Robust Argument Parsing
1#!/bin/bash
2set -Eeuo pipefail
3
4# Default values
5VERBOSE=false
6DRY_RUN=false
7OUTPUT_FILE=""
8THREADS=4
9
10usage() {
11 cat <<EOF
12Usage: $0 [OPTIONS]
13
14Options:
15 -v, --verbose Enable verbose output
16 -d, --dry-run Run without making changes
17 -o, --output FILE Output file path
18 -j, --jobs NUM Number of parallel jobs
19 -h, --help Show this help message
20EOF
21 exit "${1:-0}"
22}
23
24# Parse arguments
25while [[ $# -gt 0 ]]; do
26 case "$1" in
27 -v|--verbose)
28 VERBOSE=true
29 shift
30 ;;
31 -d|--dry-run)
32 DRY_RUN=true
33 shift
34 ;;
35 -o|--output)
36 OUTPUT_FILE="$2"
37 shift 2
38 ;;
39 -j|--jobs)
40 THREADS="$2"
41 shift 2
42 ;;
43 -h|--help)
44 usage 0
45 ;;
46 --)
47 shift
48 break
49 ;;
50 *)
51 echo "ERROR: Unknown option: $1" >&2
52 usage 1
53 ;;
54 esac
55done
56
57# Validate required arguments
58[[ -n "$OUTPUT_FILE" ]] || { echo "ERROR: -o/--output is required" >&2; usage 1; }
Pattern 5: Structured Logging
1#!/bin/bash
2set -Eeuo pipefail
3
4# Logging functions
5log_info() {
6 echo "[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $*" >&2
7}
8
9log_warn() {
10 echo "[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $*" >&2
11}
12
13log_error() {
14 echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
15}
16
17log_debug() {
18 if [[ "${DEBUG:-0}" == "1" ]]; then
19 echo "[$(date +'%Y-%m-%d %H:%M:%S')] DEBUG: $*" >&2
20 fi
21}
22
23# Usage
24log_info "Starting script"
25log_debug "Debug information"
26log_warn "Warning message"
27log_error "Error occurred"
Pattern 6: Process Orchestration with Signals
1#!/bin/bash
2set -Eeuo pipefail
3
4# Track background processes
5PIDS=()
6
7cleanup() {
8 log_info "Shutting down..."
9
10 # Terminate all background processes
11 for pid in "${PIDS[@]}"; do
12 if kill -0 "$pid" 2>/dev/null; then
13 kill -TERM "$pid" 2>/dev/null || true
14 fi
15 done
16
17 # Wait for graceful shutdown
18 for pid in "${PIDS[@]}"; do
19 wait "$pid" 2>/dev/null || true
20 done
21}
22
23trap cleanup SIGTERM SIGINT
24
25# Start background tasks
26background_task &
27PIDS+=($!)
28
29another_task &
30PIDS+=($!)
31
32# Wait for all background processes
33wait
Pattern 7: Safe File Operations
1#!/bin/bash
2set -Eeuo pipefail
3
4# Use -i flag to move safely without overwriting
5safe_move() {
6 local -r source="$1"
7 local -r dest="$2"
8
9 if [[ ! -e "$source" ]]; then
10 echo "ERROR: Source does not exist: $source" >&2
11 return 1
12 fi
13
14 if [[ -e "$dest" ]]; then
15 echo "ERROR: Destination already exists: $dest" >&2
16 return 1
17 fi
18
19 mv "$source" "$dest"
20}
21
22# Safe directory cleanup
23safe_rmdir() {
24 local -r dir="$1"
25
26 if [[ ! -d "$dir" ]]; then
27 echo "ERROR: Not a directory: $dir" >&2
28 return 1
29 fi
30
31 # Use -I flag to prompt before rm (BSD/GNU compatible)
32 rm -rI -- "$dir"
33}
34
35# Atomic file writes
36atomic_write() {
37 local -r target="$1"
38 local -r tmpfile
39 tmpfile=$(mktemp) || return 1
40
41 # Write to temp file first
42 cat > "$tmpfile"
43
44 # Atomic rename
45 mv "$tmpfile" "$target"
46}
Pattern 8: Idempotent Script Design
1#!/bin/bash
2set -Eeuo pipefail
3
4# Check if resource already exists
5ensure_directory() {
6 local -r dir="$1"
7
8 if [[ -d "$dir" ]]; then
9 log_info "Directory already exists: $dir"
10 return 0
11 fi
12
13 mkdir -p "$dir" || {
14 log_error "Failed to create directory: $dir"
15 return 1
16 }
17
18 log_info "Created directory: $dir"
19}
20
21# Ensure configuration state
22ensure_config() {
23 local -r config_file="$1"
24 local -r default_value="$2"
25
26 if [[ ! -f "$config_file" ]]; then
27 echo "$default_value" > "$config_file"
28 log_info "Created config: $config_file"
29 fi
30}
31
32# Rerunning script multiple times should be safe
33ensure_directory "/var/cache/myapp"
34ensure_config "/etc/myapp/config" "DEBUG=false"
Pattern 9: Safe Command Substitution
1#!/bin/bash
2set -Eeuo pipefail
3
4# Use $() instead of backticks
5name=$(<"$file") # Modern, safe variable assignment from file
6output=$(command -v python3) # Get command location safely
7
8# Handle command substitution with error checking
9result=$(command -v node) || {
10 log_error "node command not found"
11 return 1
12}
13
14# For multiple lines
15mapfile -t lines < <(grep "pattern" "$file")
16
17# NUL-safe iteration
18while IFS= read -r -d '' file; do
19 echo "Processing: $file"
20done < <(find /path -type f -print0)
Pattern 10: Dry-Run Support
1#!/bin/bash
2set -Eeuo pipefail
3
4DRY_RUN="${DRY_RUN:-false}"
5
6run_cmd() {
7 if [[ "$DRY_RUN" == "true" ]]; then
8 echo "[DRY RUN] Would execute: $*"
9 return 0
10 fi
11
12 "$@"
13}
14
15# Usage
16run_cmd cp "$source" "$dest"
17run_cmd rm "$file"
18run_cmd chown "$owner" "$target"
Advanced Defensive Techniques
Named Parameters Pattern
1#!/bin/bash
2set -Eeuo pipefail
3
4process_data() {
5 local input_file=""
6 local output_dir=""
7 local format="json"
8
9 # Parse named parameters
10 while [[ $# -gt 0 ]]; do
11 case "$1" in
12 --input=*)
13 input_file="${1#*=}"
14 ;;
15 --output=*)
16 output_dir="${1#*=}"
17 ;;
18 --format=*)
19 format="${1#*=}"
20 ;;
21 *)
22 echo "ERROR: Unknown parameter: $1" >&2
23 return 1
24 ;;
25 esac
26 shift
27 done
28
29 # Validate required parameters
30 [[ -n "$input_file" ]] || { echo "ERROR: --input is required" >&2; return 1; }
31 [[ -n "$output_dir" ]] || { echo "ERROR: --output is required" >&2; return 1; }
32}
Dependency Checking
1#!/bin/bash
2set -Eeuo pipefail
3
4check_dependencies() {
5 local -a missing_deps=()
6 local -a required=("jq" "curl" "git")
7
8 for cmd in "${required[@]}"; do
9 if ! command -v "$cmd" &>/dev/null; then
10 missing_deps+=("$cmd")
11 fi
12 done
13
14 if [[ ${#missing_deps[@]} -gt 0 ]]; then
15 echo "ERROR: Missing required commands: ${missing_deps[*]}" >&2
16 return 1
17 fi
18}
19
20check_dependencies
Best Practices Summary
- Always use strict mode -
set -Eeuo pipefail - Quote all variables -
"$variable"prevents word splitting - Use [[]] conditionals - More robust than [ ]
- Implement error trapping - Catch and handle errors gracefully
- Validate all inputs - Check file existence, permissions, formats
- Use functions for reusability - Prefix with meaningful names
- Implement structured logging - Include timestamps and levels
- Support dry-run mode - Allow users to preview changes
- Handle temporary files safely - Use mktemp, cleanup with trap
- Design for idempotency - Scripts should be safe to rerun
- Document requirements - List dependencies and minimum versions
- Test error paths - Ensure error handling works correctly
- Use
command -v- Safer thanwhichfor checking executables - Prefer printf over echo - More predictable across systems
Resources
- Bash Strict Mode: http://redsymbol.net/articles/unofficial-bash-strict-mode/
- Google Shell Style Guide: https://google.github.io/styleguide/shellguide.html
- Defensive BASH Programming: https://www.lifepipe.net/
What Users Are Saying
Real feedback from the community
Environment Matrix
Dependencies
Context Window
Security & Privacy
Information
- Author
- wshobson
- Updated
- 2026-01-30
- Category
- system-admin
Related Skills
Bash Defensive Patterns
Master defensive Bash programming techniques for production-grade scripts. Use when writing robust …
View Details →Bats Testing Patterns
Master Bash Automated Testing System (Bats) for comprehensive shell script testing. Use when writing …
View Details →Bats Testing Patterns
Master Bash Automated Testing System (Bats) for comprehensive shell script testing. Use when writing …
View Details →