Common Patterns
Frequently used Bash patterns, one-liners, and script templates for everyday tasks.
Script Templates
Basic Script Template
#!/bin/bash
# script_template.sh
# Description: Template for new bash scripts
# Author: Your Name
# Date: $(date +%Y-%m-%d)
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Script configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_NAME="$(basename "$0")"
LOG_FILE="/tmp/${SCRIPT_NAME%.sh}.log"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging functions
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" | tee -a "$LOG_FILE"
}
error() {
echo -e "${RED}ERROR: $*${NC}" >&2
log "ERROR: $*"
exit 1
}
warning() {
echo -e "${YELLOW}WARNING: $*${NC}" >&2
log "WARNING: $*"
}
info() {
echo -e "${GREEN}INFO: $*${NC}"
log "INFO: $*"
}
# Help function
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS] [ARGUMENTS]
Description:
Brief description of what the script does
Options:
-h, --help Show this help message
-v, --verbose Enable verbose mode
-q, --quiet Quiet mode (minimal output)
-d, --debug Enable debug mode
Examples:
$SCRIPT_NAME --help
$SCRIPT_NAME --verbose file.txt
EOF
}
# Parse command line arguments
VERBOSE=0
QUIET=0
DEBUG=0
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-v|--verbose)
VERBOSE=1
shift
;;
-q|--quiet)
QUIET=1
shift
;;
-d|--debug)
DEBUG=1
set -x
shift
;;
--)
shift
break
;;
-*)
error "Unknown option: $1"
;;
*)
break
;;
esac
done
# Main function
main() {
info "Starting $SCRIPT_NAME"
# Your main logic here
info "Completed successfully"
}
# Cleanup function
cleanup() {
info "Cleaning up..."
# Add cleanup code here
}
# Set trap for cleanup
trap cleanup EXIT
# Run main function
main "$@"
Service Script Template
#!/bin/bash
# service_template.sh
# Service management script template
SERVICE_NAME="myservice"
SERVICE_USER="myuser"
SERVICE_DIR="/opt/myservice"
PID_FILE="/var/run/${SERVICE_NAME}.pid"
LOG_FILE="/var/log/${SERVICE_NAME}.log"
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" >&2
exit 1
fi
# Service functions
start_service() {
if is_running; then
echo "$SERVICE_NAME is already running"
return 0
fi
echo "Starting $SERVICE_NAME..."
cd "$SERVICE_DIR"
sudo -u "$SERVICE_USER" ./start.sh > "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
sleep 2
if is_running; then
echo "$SERVICE_NAME started successfully"
else
echo "Failed to start $SERVICE_NAME"
return 1
fi
}
stop_service() {
if ! is_running; then
echo "$SERVICE_NAME is not running"
return 0
fi
echo "Stopping $SERVICE_NAME..."
local pid=$(cat "$PID_FILE")
kill "$pid"
# Wait for process to stop
for i in {1..30}; do
if ! is_running; then
rm -f "$PID_FILE"
echo "$SERVICE_NAME stopped"
return 0
fi
sleep 1
done
# Force kill if still running
echo "Force killing $SERVICE_NAME..."
kill -9 "$pid" 2>/dev/null
rm -f "$PID_FILE"
}
restart_service() {
stop_service
start_service
}
status_service() {
if is_running; then
local pid=$(cat "$PID_FILE")
echo "$SERVICE_NAME is running (PID: $pid)"
else
echo "$SERVICE_NAME is not running"
fi
}
is_running() {
[[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null
}
# Main logic
case "${1:-}" in
start)
start_service
;;
stop)
stop_service
;;
restart)
restart_service
;;
status)
status_service
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
Configuration Management
Configuration File Pattern
#!/bin/bash
# config_pattern.sh
# Default configuration
DEFAULT_CONFIG="/etc/myapp/config.conf"
USER_CONFIG="$HOME/.myapp/config.conf"
LOCAL_CONFIG="./config.conf"
# Configuration variables with defaults
CONFIG_VAR1="default_value1"
CONFIG_VAR2="default_value2"
CONFIG_VAR3="default_value3"
# Load configuration function
load_config() {
local config_file="$1"
if [[ -f "$config_file" ]]; then
echo "Loading configuration from $config_file"
# Source the config file safely
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ $key =~ ^[[:space:]]*# ]] && continue
[[ -z "$key" ]] && continue
# Remove leading/trailing whitespace
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
# Set variable
declare -g "$key=$value"
done < "$config_file"
fi
}
# Load configuration in order (later files override earlier ones)
load_config "$DEFAULT_CONFIG"
load_config "$USER_CONFIG"
load_config "$LOCAL_CONFIG"
# Example config file format:
# # Configuration file
# CONFIG_VAR1=custom_value1
# CONFIG_VAR2=custom_value2
# # CONFIG_VAR3 uses default value
Environment Management
#!/bin/bash
# environment_management.sh
# Environment detection
detect_environment() {
if [[ -n "${PRODUCTION:-}" ]]; then
echo "production"
elif [[ -n "${STAGING:-}" ]]; then
echo "staging"
elif [[ -n "${DEVELOPMENT:-}" ]]; then
echo "development"
else
echo "unknown"
fi
}
# Environment-specific configuration
load_env_config() {
local env=$(detect_environment)
case "$env" in
production)
LOG_LEVEL="ERROR"
DEBUG=0
DB_HOST="prod-db.company.com"
;;
staging)
LOG_LEVEL="WARN"
DEBUG=0
DB_HOST="staging-db.company.com"
;;
development)
LOG_LEVEL="DEBUG"
DEBUG=1
DB_HOST="localhost"
;;
*)
echo "Unknown environment: $env" >&2
exit 1
;;
esac
echo "Environment: $env"
echo "Log level: $LOG_LEVEL"
echo "Debug: $DEBUG"
echo "DB Host: $DB_HOST"
}
load_env_config
Data Processing Patterns
CSV Processing
#!/bin/bash
# csv_processing.sh
# Process CSV file
process_csv() {
local csv_file="$1"
local output_file="$2"
# Check if file exists
if [[ ! -f "$csv_file" ]]; then
echo "Error: CSV file not found: $csv_file" >&2
return 1
fi
# Read CSV header
IFS=',' read -r -a headers < "$csv_file"
echo "Headers: ${headers[*]}"
# Process each row
local line_number=1
while IFS=',' read -r -a fields; do
# Skip header
[[ $line_number -eq 1 ]] && { line_number=$((line_number + 1)); continue; }
# Process fields
echo "Row $line_number:"
for i in "${!fields[@]}"; do
echo " ${headers[$i]}: ${fields[$i]}"
done
line_number=$((line_number + 1))
done < "$csv_file"
}
# Filter CSV by column value
filter_csv() {
local csv_file="$1"
local column_index="$2"
local filter_value="$3"
awk -F',' -v col="$column_index" -v val="$filter_value" \
'NR==1 || $col == val' "$csv_file"
}
# Sort CSV by column
sort_csv() {
local csv_file="$1"
local column_index="$2"
(head -n 1 "$csv_file" && tail -n +2 "$csv_file" | sort -t',' -k"$column_index")
}
JSON Processing
#!/bin/bash
# json_processing.sh
# Process JSON with jq
process_json() {
local json_file="$1"
# Check if jq is available
if ! command -v jq > /dev/null 2>&1; then
echo "Error: jq is not installed" >&2
return 1
fi
# Extract specific fields
jq -r '.[] | .name + ": " + .value' "$json_file"
# Filter objects
jq '.[] | select(.status == "active")' "$json_file"
# Transform data
jq 'map({id: .id, name: .name, active: (.status == "active")})' "$json_file"
}
# JSON without jq (basic parsing)
parse_json_basic() {
local json_file="$1"
# Extract simple values
grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$json_file" | \
cut -d'"' -f4
}
# Create JSON
create_json() {
local name="$1"
local value="$2"
cat << EOF
{
"name": "$name",
"value": "$value",
"timestamp": "$(date -Iseconds)"
}
EOF
}
File Management Patterns
Backup Pattern
#!/bin/bash
# backup_pattern.sh
# Create backup with timestamp
backup_file() {
local file="$1"
local backup_dir="${2:-./backups}"
if [[ ! -f "$file" ]]; then
echo "Error: File not found: $file" >&2
return 1
fi
mkdir -p "$backup_dir"
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_name="$(basename "$file").backup.$timestamp"
local backup_path="$backup_dir/$backup_name"
cp "$file" "$backup_path"
echo "Backup created: $backup_path"
}
# Rotate backups (keep only N newest)
rotate_backups() {
local backup_dir="$1"
local keep_count="$2"
local backup_files=("$backup_dir"/*.backup.*)
local file_count=${#backup_files[@]}
if [[ $file_count -gt $keep_count ]]; then
# Sort by modification time and remove oldest
local to_remove=$((file_count - keep_count))
ls -t "$backup_dir"/*.backup.* | tail -n "$to_remove" | xargs rm -f
echo "Removed $to_remove old backups"
fi
}
# Incremental backup
incremental_backup() {
local source_dir="$1"
local backup_dir="$2"
local last_backup="$backup_dir/last_backup"
mkdir -p "$backup_dir"
if [[ -f "$last_backup" ]]; then
# Find files newer than last backup
find "$source_dir" -newer "$last_backup" -type f | \
while read -r file; do
backup_file "$file" "$backup_dir"
done
else
# First backup - backup everything
find "$source_dir" -type f | \
while read -r file; do
backup_file "$file" "$backup_dir"
done
fi
touch "$last_backup"
}
File Synchronization
#!/bin/bash
# file_sync.sh
# Sync directories
sync_directories() {
local source="$1"
local dest="$2"
local dry_run="${3:-false}"
if [[ ! -d "$source" ]]; then
echo "Error: Source directory not found: $source" >&2
return 1
fi
mkdir -p "$dest"
local rsync_opts="-avz --delete"
if [[ "$dry_run" == "true" ]]; then
rsync_opts="$rsync_opts --dry-run"
fi
rsync $rsync_opts "$source/" "$dest/"
}
# Watch directory for changes
watch_directory() {
local watch_dir="$1"
local callback="$2"
# Check if inotifywait is available
if ! command -v inotifywait > /dev/null 2>&1; then
echo "Error: inotifywait is not installed" >&2
return 1
fi
echo "Watching $watch_dir for changes..."
inotifywait -m -r -e modify,create,delete "$watch_dir" | \
while read -r path action file; do
echo "File $action: $path$file"
$callback "$path$file" "$action"
done
}
Network Patterns
API Client Pattern
#!/bin/bash
# api_client.sh
# API configuration
API_BASE_URL="https://api.example.com"
API_KEY=""
API_VERSION="v1"
# Make API request
api_request() {
local method="$1"
local endpoint="$2"
local data="$3"
local url="$API_BASE_URL/$API_VERSION/$endpoint"
local headers=()
if [[ -n "$API_KEY" ]]; then
headers+=("-H" "Authorization: Bearer $API_KEY")
fi
headers+=("-H" "Content-Type: application/json")
case "$method" in
GET)
curl -s "${headers[@]}" "$url"
;;
POST)
curl -s "${headers[@]}" -X POST -d "$data" "$url"
;;
PUT)
curl -s "${headers[@]}" -X PUT -d "$data" "$url"
;;
DELETE)
curl -s "${headers[@]}" -X DELETE "$url"
;;
*)
echo "Error: Unsupported method: $method" >&2
return 1
;;
esac
}
# API wrapper functions
api_get() {
api_request "GET" "$1"
}
api_post() {
api_request "POST" "$1" "$2"
}
api_put() {
api_request "PUT" "$1" "$2"
}
api_delete() {
api_request "DELETE" "$1"
}
# Usage examples
# api_get "users"
# api_post "users" '{"name": "John", "email": "john@example.com"}'
Download Pattern
#!/bin/bash
# download_pattern.sh
# Download with retry
download_with_retry() {
local url="$1"
local output="$2"
local max_attempts="${3:-3}"
local delay="${4:-5}"
local attempt=1
while [[ $attempt -le $max_attempts ]]; do
echo "Download attempt $attempt of $max_attempts..."
if curl -L -o "$output" "$url"; then
echo "Download successful"
return 0
else
echo "Download failed (attempt $attempt)"
if [[ $attempt -lt $max_attempts ]]; then
echo "Retrying in ${delay}s..."
sleep "$delay"
fi
fi
attempt=$((attempt + 1))
done
echo "Download failed after $max_attempts attempts"
return 1
}
# Download with progress
download_with_progress() {
local url="$1"
local output="$2"
curl -L --progress-bar -o "$output" "$url"
}
# Parallel downloads
parallel_downloads() {
local -n urls=$1
local output_dir="$2"
local max_parallel="${3:-5}"
local active_jobs=0
for url in "${urls[@]}"; do
local filename=$(basename "$url")
local output_path="$output_dir/$filename"
# Start download in background
download_with_retry "$url" "$output_path" &
active_jobs=$((active_jobs + 1))
# Wait if max parallel jobs reached
if [[ $active_jobs -ge $max_parallel ]]; then
wait -n # Wait for any job to complete
active_jobs=$((active_jobs - 1))
fi
done
# Wait for all downloads to complete
wait
}
Monitoring Patterns
Health Check Pattern
#!/bin/bash
# health_check.sh
# Service health check
check_service_health() {
local service_name="$1"
local health_url="$2"
local timeout="${3:-10}"
echo "Checking health of $service_name..."
# Check if service is running
if ! systemctl is-active --quiet "$service_name"; then
echo "CRITICAL: $service_name is not running"
return 2
fi
# Check HTTP endpoint
if [[ -n "$health_url" ]]; then
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$timeout" "$health_url")
if [[ "$http_code" -eq 200 ]]; then
echo "OK: $service_name is healthy"
return 0
else
echo "WARNING: $service_name returned HTTP $http_code"
return 1
fi
fi
echo "OK: $service_name is running"
return 0
}
# System health check
check_system_health() {
local warnings=0
local errors=0
# Check disk space
local disk_usage
disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [[ $disk_usage -gt 90 ]]; then
echo "CRITICAL: Disk usage is ${disk_usage}%"
errors=$((errors + 1))
elif [[ $disk_usage -gt 80 ]]; then
echo "WARNING: Disk usage is ${disk_usage}%"
warnings=$((warnings + 1))
fi
# Check memory usage
local memory_usage
memory_usage=$(free | awk '/Mem:/ {printf("%.0f", $3/$2*100)}')
if [[ $memory_usage -gt 90 ]]; then
echo "CRITICAL: Memory usage is ${memory_usage}%"
errors=$((errors + 1))
elif [[ $memory_usage -gt 80 ]]; then
echo "WARNING: Memory usage is ${memory_usage}%"
warnings=$((warnings + 1))
fi
# Check load average
local load_avg
load_avg=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
local cpu_count=$(nproc)
local load_ratio=$(echo "scale=2; $load_avg / $cpu_count" | bc)
if [[ $(echo "$load_ratio > 2" | bc) -eq 1 ]]; then
echo "CRITICAL: Load average is high ($load_avg)"
errors=$((errors + 1))
elif [[ $(echo "$load_ratio > 1.5" | bc) -eq 1 ]]; then
echo "WARNING: Load average is high ($load_avg)"
warnings=$((warnings + 1))
fi
# Return appropriate exit code
if [[ $errors -gt 0 ]]; then
return 2
elif [[ $warnings -gt 0 ]]; then
return 1
else
echo "OK: System is healthy"
return 0
fi
}
Log Monitoring Pattern
#!/bin/bash
# log_monitor.sh
# Monitor log file for patterns
monitor_log() {
local log_file="$1"
local pattern="$2"
local action="$3"
if [[ ! -f "$log_file" ]]; then
echo "Error: Log file not found: $log_file" >&2
return 1
fi
echo "Monitoring $log_file for pattern: $pattern"
tail -f "$log_file" | while read -r line; do
if [[ "$line" =~ $pattern ]]; then
echo "Pattern matched: $line"
$action "$line"
fi
done
}
# Log rotation check
check_log_rotation() {
local log_file="$1"
local max_size="$2" # in MB
if [[ ! -f "$log_file" ]]; then
return 0
fi
local file_size
file_size=$(stat -c%s "$log_file")
local max_size_bytes=$((max_size * 1024 * 1024))
if [[ $file_size -gt $max_size_bytes ]]; then
echo "Log file $log_file is larger than ${max_size}MB, rotating..."
# Simple rotation
mv "$log_file" "${log_file}.$(date +%Y%m%d_%H%M%S)"
touch "$log_file"
# Restart service if needed
# systemctl restart myservice
fi
}
Deployment Patterns
Zero-Downtime Deployment
#!/bin/bash
# zero_downtime_deploy.sh
# Blue-green deployment
deploy_blue_green() {
local new_version="$1"
local service_name="$2"
local health_check_url="$3"
echo "Starting blue-green deployment for $service_name v$new_version"
# Determine current and new environments
local current_env
if systemctl is-active --quiet "${service_name}-blue"; then
current_env="blue"
new_env="green"
else
current_env="green"
new_env="blue"
fi
echo "Current environment: $current_env"
echo "Deploying to: $new_env"
# Deploy to new environment
deploy_to_environment "$new_env" "$new_version" "$service_name"
# Health check
if ! check_service_health "${service_name}-${new_env}" "$health_check_url"; then
echo "Health check failed, aborting deployment"
return 1
fi
# Switch traffic
echo "Switching traffic to $new_env"
update_load_balancer "$new_env"
# Stop old environment
echo "Stopping old environment: $current_env"
systemctl stop "${service_name}-${current_env}"
echo "Deployment completed successfully"
}
# Rolling deployment
deploy_rolling() {
local new_version="$1"
local service_name="$2"
local instances=("$@")
echo "Starting rolling deployment for $service_name v$new_version"
for instance in "${instances[@]}"; do
echo "Deploying to instance: $instance"
# Remove from load balancer
remove_from_load_balancer "$instance"
# Deploy new version
deploy_to_instance "$instance" "$new_version"
# Health check
if check_instance_health "$instance"; then
# Add back to load balancer
add_to_load_balancer "$instance"
echo "Instance $instance deployed successfully"
else
echo "Instance $instance failed health check"
return 1
fi
# Wait before next instance
sleep 30
done
echo "Rolling deployment completed"
}
These patterns provide a solid foundation for common Bash scripting tasks. They can be adapted and combined to create more complex solutions for specific needs.
See Advanced Features for more sophisticated patterns and System Operations for system-level patterns.