Transform your Bash scripts from linear commands to modular, reusable, and maintainable automation tools. Master functions and argument handling to create professional-grade DevOps scripts.
Define, call, and organize functions
Syntax, scope, return values
Local vs global variables
Pass data to functions
Positional, named, default args
shift, getopts, arrays
Real DevOps patterns
Error handling, logging
Library functions, sourcing
Why Functions Matter for DevOps
Functions turn your scripts from spaghetti code into well-organized, reusable components. Benefits:
Part 1: Function Fundamentals
Basic Function Syntax
Different Ways to Define Functions
#!/bin/bash
# Method 1: function keyword (most readable)
function backup_files() {
echo "Backing up files..."
# Implementation
}
# Method 2: Parentheses (POSIX style)
restart_service() {
echo "Restarting service..."
# Implementation
}
# Method 3: Mixed style
function check_disk_space {
echo "Checking disk space..."
# Implementation
}
# Method 4: One-liner simple functions
log_info() { echo "[INFO] $1"; }
log_error() { echo "[ERROR] $1" >&2; }
log_debug() { [[ $DEBUG ]] && echo "[DEBUG] $1"; }
# Method 5: Function that calls another function
full_backup() {
log_info "Starting full backup"
backup_files
log_info "Backup complete"
}
# Call functions
backup_files
restart_service
check_disk_space
log_info "Process started"
log_error "Something went wrong"
full_backup
Function Scope and Variables
#!/bin/bash
# Global variable
GLOBAL_VAR="I am global"
function demonstrate_scope() {
# Local variable (only exists in function)
local LOCAL_VAR="I am local"
# This modifies global variable (BAD PRACTICE)
GLOBAL_VAR="Modified inside function"
# Create new global (also BAD PRACTICE)
NEW_GLOBAL="Created inside function"
echo "Inside function:"
echo " LOCAL_VAR: $LOCAL_VAR"
echo " GLOBAL_VAR: $GLOBAL_VAR"
echo " NEW_GLOBAL: $NEW_GLOBAL"
}
# Call function
demonstrate_scope
echo ""
echo "Outside function:"
echo " LOCAL_VAR: ${LOCAL_VAR:-undefined}"
echo " GLOBAL_VAR: $GLOBAL_VAR"
echo " NEW_GLOBAL: ${NEW_GLOBAL:-undefined}"
# Better practice: Pass values as arguments, return via echo
function calculate_sum() {
local a=$1
local b=$2
local sum=$((a + b))
echo "$sum" # "Return" value
}
RESULT=$(calculate_sum 10 20)
echo "Sum: $RESULT"
# Even better: Use reference variables (bash 4.3+)
function calculate_stats() {
local input_array=("$@")
local -n sum_ref=$1 # Reference to variable outside
local -n avg_ref=$2 # Another reference
sum_ref=0
for num in "${input_array[@]}"; do
sum_ref=$((sum_ref + num))
done
avg_ref=$((sum_ref / ${#input_array[@]}))
}
NUMBERS=(10 20 30 40 50)
TOTAL=0
AVERAGE=0
calculate_stats NUMBERS TOTAL AVERAGE
echo "Total: $TOTAL, Average: $AVERAGE"
local for function variables to avoid polluting the global namespace and causing hard-to-debug issues.
Part 2: Function Arguments & Parameters
$0
$1
$2
$@
$#
Positional Arguments
#!/bin/bash
# Basic positional arguments
function greet_user() {
local name=$1
local time_of_day=${2:-"Morning"} # Default value
echo "Good $time_of_day, $name!"
}
greet_user "Alice"
greet_user "Bob" "Evening"
greet_user "Charlie" "Afternoon"
# Multiple arguments
function create_user() {
local username=$1
local full_name=$2
local shell=${3:-"/bin/bash"}
local home_dir=${4:-"/home/$username"}
echo "Creating user: $username"
echo " Full name: $full_name"
echo " Shell: $shell"
echo " Home: $home_dir"
# sudo useradd -m -s "$shell" -c "$full_name" "$username"
}
create_user "alice" "Alice Johnson"
create_user "bob" "Bob Smith" "/bin/zsh" "/opt/bob"
# Process all arguments
function process_files() {
echo "Processing $# files:"
local count=1
for file in "$@"; do
echo " $count. $file"
# Process each file
((count++))
done
}
process_files file1.txt file2.txt file3.txt
# Shift through arguments
function parse_options() {
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
VERBOSE=true
shift
;;
-f|--file)
FILENAME=$2
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
echo "Verbose: ${VERBOSE:-false}"
echo "Filename: ${FILENAME:-not set}"
}
parse_options --verbose --file data.txt
Named Arguments with getopts
#!/bin/bash
# Professional argument parsing
function parse_arguments() {
local OPTIND
local options
# Default values
local verbose=false
local output_file=""
local count=1
while getopts ":vf:o:c:h" opt; do
case $opt in
v)
verbose=true
;;
f)
input_file="$OPTARG"
;;
o)
output_file="$OPTARG"
;;
c)
count="$OPTARG"
if ! [[ $count =~ ^[0-9]+$ ]]; then
echo "ERROR: Count must be a number" >&2
return 1
fi
;;
h)
show_help
return 0
;;
\?)
echo "Invalid option: -$OPTARG" >&2
return 1
;;
:)
echo "Option -$OPTARG requires an argument" >&2
return 1
;;
esac
done
shift $((OPTIND - 1))
# Remaining arguments (non-options)
local remaining_args=("$@")
# Return values via global (for this example)
PARSE_RESULT_VERBOSE=$verbose
PARSE_RESULT_OUTPUT=$output_file
PARSE_RESULT_COUNT=$count
PARSE_RESULT_INPUT=${input_file:-""}
PARSE_RESULT_REMAINING=("${remaining_args[@]}")
}
function show_help() {
cat << EOF
Usage: $0 [OPTIONS] [FILES...]
Options:
-v Verbose output
-f FILE Input file
-o FILE Output file
-c COUNT Number of iterations
-h Show this help message
Examples:
$0 -v -f input.txt -o output.txt
$0 -c 5 file1 file2
EOF
}
# Test the function
parse_arguments -v -f input.txt -o output.txt -c 3 file1 file2 file3
echo "Verbose: $PARSE_RESULT_VERBOSE"
echo "Input file: $PARSE_RESULT_INPUT"
echo "Output file: $PARSE_RESULT_OUTPUT"
echo "Count: $PARSE_RESULT_COUNT"
echo "Remaining: ${PARSE_RESULT_REMAINING[*]}"
Advanced Argument Patterns
#!/bin/bash
# Pattern 1: Required vs optional arguments
function deploy_app() {
# Required arguments
if [[ $# -lt 2 ]]; then
echo "Usage: deploy_app [options...]"
return 1
fi
local environment=$1
local version=$2
shift 2 # Remove first two arguments
# Optional arguments
local rollback=false
local dry_run=false
local force=false
while [[ $# -gt 0 ]]; do
case $1 in
--rollback) rollback=true ;;
--dry-run) dry_run=true ;;
--force) force=true ;;
*) echo "Unknown option: $1"; return 1 ;;
esac
shift
done
echo "Deploying version $version to $environment"
echo " Rollback: $rollback"
echo " Dry run: $dry_run"
echo " Force: $force"
}
deploy_app staging v1.2.3
deploy_app production v1.2.4 --dry-run --force
# Pattern 2: Array arguments
function process_items() {
local items=("$@") # All arguments as array
echo "Processing ${#items[@]} items:"
for item in "${items[@]}"; do
echo " - $item"
done
}
process_items "apple" "banana" "cherry"
process_items "server1" "server2" "server3" "server4"
# Pattern 3: Key-value arguments
function configure_service() {
declare -A config
while [[ $# -gt 0 ]]; do
if [[ $1 == *=* ]]; then
key="${1%%=*}"
value="${1#*=}"
config["$key"]="$value"
else
config["$1"]=true
fi
shift
done
echo "Configuration:"
for key in "${!config[@]}"; do
echo " $key = ${config[$key]}"
done
}
configure_service port=8080 host=localhost ssl=true debug
# Pattern 4: Subcommand pattern (like git)
function app_cli() {
local command=$1
shift
case $command in
start)
start_server "$@"
;;
stop)
stop_server "$@"
;;
restart)
restart_server "$@"
;;
status)
check_status "$@"
;;
*)
echo "Unknown command: $command"
echo "Available: start, stop, restart, status"
return 1
;;
esac
}
function start_server() {
local port=${1:-8080}
echo "Starting server on port $port"
}
function stop_server() {
echo "Stopping server"
}
# Test subcommands
app_cli start 9090
app_cli status
Part 3: Return Values and Exit Codes
Understanding Return Values
return Nreturn 0 (success)echo "value"result=$(func)declare -nfunc $result_refglobal variableRESULT="value"#!/bin/bash
# Method 1: Exit codes (0 = success, non-zero = error)
function check_service() {
local service_name=$1
if systemctl is-active --quiet "$service_name"; then
return 0 # Service is running
else
return 1 # Service is not running
fi
}
check_service nginx
if [ $? -eq 0 ]; then
echo "Nginx is running"
else
echo "Nginx is not running"
fi
# Method 2: Capture output
function get_system_info() {
local hostname=$(hostname)
local uptime=$(uptime -p)
local memory=$(free -h | awk '/^Mem:/ {print $2}')
# Return multiple values as JSON/CSV
echo "hostname:$hostname,uptime:$uptime,memory:$memory"
}
INFO=$(get_system_info)
echo "System info: $INFO"
# Parse the returned data
IFS=',' read -ra PARTS <<< "$INFO"
for part in "${PARTS[@]}"; do
IFS=':' read -r key value <<< "$part"
echo "$key: $value"
done
# Method 3: Multiple return values via arrays
function get_stats() {
local numbers=("$@")
local sum=0
local min=${numbers[0]}
local max=${numbers[0]}
for num in "${numbers[@]}"; do
sum=$((sum + num))
if [ $num -lt $min ]; then min=$num; fi
if [ $num -gt $max ]; then max=$num; fi
done
local avg=$((sum / ${#numbers[@]}))
# Return as array
echo "$sum $avg $min $max"
}
DATA=(10 20 30 40 50)
STATS=($(get_stats "${DATA[@]}"))
echo "Sum: ${STATS[0]}, Avg: ${STATS[1]}, Min: ${STATS[2]}, Max: ${STATS[3]}"
# Method 4: Using reference variables (bash 4.3+)
function calculate_metrics() {
local numbers=("$@")
local -n sum_ref=$1 # Reference to variable
local -n avg_ref=$2 # Another reference
sum_ref=0
for num in "${numbers[@]:2}"; do # Skip first two (the references)
sum_ref=$((sum_ref + num))
done
avg_ref=$((sum_ref / (${#numbers[@]} - 2)))
}
DATA=(10 20 30 40 50)
TOTAL=0
AVERAGE=0
calculate_metrics TOTAL AVERAGE "${DATA[@]}"
echo "Total: $TOTAL, Average: $AVERAGE"
# Method 5: Complex return as associative array
function get_server_stats() {
declare -A stats
stats["cpu"]=$(grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage "%"}')
stats["memory"]=$(free -m | awk '/^Mem:/ {print $3 "/" $2 "MB"}')
stats["disk"]=$(df -h / | awk 'NR==2 {print $3 "/" $2}')
stats["uptime"]=$(uptime -p)
# Return by echoing array declaration
declare -p stats
}
# Evaluate the returned array declaration
eval "$(get_server_stats)"
echo "CPU: ${stats[cpu]}"
echo "Memory: ${stats[memory]}"
echo "Disk: ${stats[disk]}"
echo "Uptime: ${stats[uptime]}"
Part 4: Practical DevOps Function Libraries
Common Utility Functions
#!/bin/bash
# lib-utils.sh - Common DevOps utility functions
set -euo pipefail
# Color output functions
function log_info() {
echo -e "\033[0;32m[INFO]\033[0m $1"
}
function log_warn() {
echo -e "\033[1;33m[WARN]\033[0m $1" >&2
}
function log_error() {
echo -e "\033[0;31m[ERROR]\033[0m $1" >&2
}
function log_debug() {
if [[ ${DEBUG:-false} == true ]]; then
echo -e "\033[0;36m[DEBUG]\033[0m $1"
fi
}
# Validation functions
function validate_number() {
local value=$1
local min=${2:-""}
local max=${3:-""}
if ! [[ $value =~ ^[0-9]+$ ]]; then
log_error "Value must be a number: $value"
return 1
fi
if [[ -n $min ]] && [ $value -lt $min ]; then
log_error "Value must be >= $min: $value"
return 1
fi
if [[ -n $max ]] && [ $value -gt $max ]; then
log_error "Value must be <= $max: $value"
return 1
fi
}
function validate_file() {
local file=$1
local check_readable=${2:-false}
local check_writable=${3:-false}
if [[ ! -e $file ]]; then
log_error "File does not exist: $file"
return 1
fi
if [[ ! -f $file ]]; then
log_error "Not a regular file: $file"
return 1
fi
if [[ $check_readable == true ]] && [[ ! -r $file ]]; then
log_error "File not readable: $file"
return 1
fi
if [[ $check_writable == true ]] && [[ ! -w $file ]]; then
log_error "File not writable: $file"
return 1
fi
}
function validate_directory() {
local dir=$1
local check_writable=${2:-false}
if [[ ! -d $dir ]]; then
log_error "Not a directory: $dir"
return 1
fi
if [[ $check_writable == true ]] && [[ ! -w $dir ]]; then
log_error "Directory not writable: $dir"
return 1
fi
}
# Network functions
function check_port() {
local host=${1:-"localhost"}
local port=$2
local timeout=${3:-5}
if timeout $timeout bash -c "cat < /dev/null > /dev/tcp/$host/$port" 2>/dev/null; then
log_debug "Port $port on $host is open"
return 0
else
log_debug "Port $port on $host is closed"
return 1
fi
}
function wait_for_service() {
local host=$1
local port=$2
local timeout=${3:-60}
local interval=${4:-2}
local start_time=$(date +%s)
local end_time=$((start_time + timeout))
log_info "Waiting for $host:$port (timeout: ${timeout}s)"
while [[ $(date +%s) -lt $end_time ]]; do
if check_port "$host" "$port" 1; then
log_info "Service $host:$port is ready"
return 0
fi
sleep $interval
echo -n "."
done
log_error "Timeout waiting for $host:$port"
return 1
}
# System functions
function get_system_load() {
local load1 load5 load15
read -r load1 load5 load15 < /proc/loadavg
echo "1min:$load1,5min:$load5,15min:$load15"
}
function get_memory_usage() {
local total used free
read -r total used free <<< $(free -m | awk '/^Mem:/ {print $2, $3, $4}')
local percent=$((used * 100 / total))
echo "$percent% (${used}MB/${total}MB)"
}
function get_disk_usage() {
local mount=$1
local usage=$(df -h "$mount" | awk 'NR==2 {print $5}')
local available=$(df -h "$mount" | awk 'NR==2 {print $4}')
echo "$usage used, $available available"
}
# String manipulation functions
function string_contains() {
local string=$1
local substring=$2
if [[ $string == *"$substring"* ]]; then
return 0
else
return 1
fi
}
function string_starts_with() {
local string=$1
local prefix=$2
if [[ $string == "$prefix"* ]]; then
return 0
else
return 1
fi
}
function string_ends_with() {
local string=$1
local suffix=$2
if [[ $string == *"$suffix" ]]; then
return 0
else
return 1
fi
}
function string_to_lower() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}
function string_to_upper() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
# Array functions
function array_contains() {
local array_name=$1
local value=$2
local -n arr=$array_name
for element in "${arr[@]}"; do
if [[ $element == "$value" ]]; then
return 0
fi
done
return 1
}
function array_join() {
local delimiter=$1
shift
local array=("$@")
local result=""
for element in "${array[@]}"; do
if [[ -z $result ]]; then
result="$element"
else
result="$result$delimiter$element"
fi
done
echo "$result"
}
# Export functions for use in other scripts
export -f log_info log_warn log_error log_debug
export -f validate_number validate_file validate_directory
export -f check_port wait_for_service
export -f get_system_load get_memory_usage get_disk_usage
Deployment Functions Library
#!/bin/bash
# lib-deploy.sh - Deployment functions for DevOps
set -euo pipefail
# Source utilities if needed
if [[ -f lib-utils.sh ]]; then
source lib-utils.sh
fi
function deploy_preflight_check() {
local environment=$1
local version=$2
log_info "Running preflight checks for $version deployment to $environment"
# Check disk space
local disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $disk_usage -gt 90 ]; then
log_error "Disk usage too high: ${disk_usage}%"
return 1
fi
# Check memory
local mem_free=$(free -m | awk '/^Mem:/ {print $7}')
if [ $mem_free -lt 512 ]; then
log_warn "Low memory: ${mem_free}MB free"
fi
# Check required services
local required_services=("docker" "nginx" "postgresql")
for service in "${required_services[@]}"; do
if ! systemctl is-active --quiet "$service"; then
log_error "Required service not running: $service"
return 1
fi
done
# Validate version format
if ! [[ $version =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
log_error "Invalid version format: $version (expected vX.Y.Z)"
return 1
fi
log_info "Preflight checks passed"
return 0
}
function deploy_backup() {
local backup_dir=$1
local app_dir=$2
log_info "Creating backup"
validate_directory "$backup_dir" true || return 1
validate_directory "$app_dir" true || return 1
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$backup_dir/backup_$timestamp.tar.gz"
if tar -czf "$backup_file" -C "$app_dir" . 2>/dev/null; then
local size=$(du -h "$backup_file" | cut -f1)
log_info "Backup created: $backup_file ($size)"
# Clean old backups (keep last 7)
find "$backup_dir" -name "backup_*.tar.gz" -mtime +7 -delete
else
log_error "Backup failed"
return 1
fi
}
function deploy_pull_code() {
local repo_url=$1
local branch=$2
local target_dir=$3
log_info "Pulling code from $repo_url ($branch)"
if [[ -d "$target_dir/.git" ]]; then
cd "$target_dir"
if ! git pull origin "$branch"; then
log_error "Git pull failed"
return 1
fi
else
if ! git clone -b "$branch" "$repo_url" "$target_dir"; then
log_error "Git clone failed"
return 1
fi
fi
log_info "Code updated successfully"
}
function deploy_install_deps() {
local app_dir=$1
log_info "Installing dependencies"
cd "$app_dir"
# Node.js projects
if [[ -f "package.json" ]]; then
log_info "Installing npm dependencies"
if ! npm ci --production; then
log_error "npm install failed"
return 1
fi
fi
# Python projects
if [[ -f "requirements.txt" ]]; then
log_info "Installing Python dependencies"
if ! pip install -r requirements.txt; then
log_error "pip install failed"
return 1
fi
fi
# Ruby projects
if [[ -f "Gemfile" ]]; then
log_info "Installing Ruby dependencies"
if ! bundle install --deployment; then
log_error "bundle install failed"
return 1
fi
fi
log_info "Dependencies installed"
}
function deploy_run_migrations() {
local app_dir=$1
local environment=$2
log_info "Running database migrations"
cd "$app_dir"
# Django migrations
if [[ -f "manage.py" ]]; then
export DJANGO_SETTINGS_MODULE="config.settings.$environment"
if ! python manage.py migrate; then
log_error "Django migrations failed"
return 1
fi
fi
# Rails migrations
if [[ -f "db/migrate" ]]; then
export RAILS_ENV="$environment"
if ! bundle exec rails db:migrate; then
log_error "Rails migrations failed"
return 1
fi
fi
log_info "Migrations completed"
}
function deploy_restart_services() {
local services=("$@")
log_info "Restarting services"
for service in "${services[@]}"; do
log_info "Restarting $service"
if ! systemctl restart "$service"; then
log_error "Failed to restart $service"
return 1
fi
# Wait for service to start
local attempts=0
local max_attempts=10
while [ $attempts -lt $max_attempts ]; do
if systemctl is-active --quiet "$service"; then
log_info "$service is running"
break
fi
((attempts++))
sleep 2
done
if [ $attempts -eq $max_attempts ]; then
log_error "$service failed to start"
return 1
fi
done
log_info "Services restarted successfully"
}
function deploy_health_check() {
local url=$1
local timeout=${2:-30}
log_info "Running health check: $url"
local start_time=$(date +%s)
local end_time=$((start_time + timeout))
while [[ $(date +%s) -lt $end_time ]]; do
if curl -s -f "$url" > /dev/null; then
log_info "Health check passed"
return 0
fi
sleep 2
echo -n "."
done
log_error "Health check failed: $url not responding"
return 1
}
function deploy_rollback() {
local backup_dir=$1
local app_dir=$2
log_warn "Initiating rollback"
# Find latest backup
local latest_backup=$(find "$backup_dir" -name "backup_*.tar.gz" -type f | sort -r | head -1)
if [[ -z $latest_backup ]]; then
log_error "No backup found for rollback"
return 1
fi
log_info "Rolling back to: $(basename "$latest_backup")"
# Stop services
systemctl stop app.service 2>/dev/null || true
# Restore backup
if tar -xzf "$latest_backup" -C "$app_dir" --strip-components=0 2>/dev/null; then
log_info "Backup restored"
# Restart services
deploy_restart_services "app.service"
# Health check
deploy_health_check "http://localhost:8080/health"
log_info "Rollback completed successfully"
return 0
else
log_error "Rollback failed"
return 1
fi
}
# Main deployment function
function deploy_application() {
local environment=$1
local version=$2
shift 2
# Parse options
local rollback=false
local dry_run=false
local force=false
while [[ $# -gt 0 ]]; do
case $1 in
--rollback) rollback=true ;;
--dry-run) dry_run=true ;;
--force) force=true ;;
*) log_error "Unknown option: $1"; return 1 ;;
esac
shift
done
log_info "Starting deployment: $version to $environment"
log_info "Options: rollback=$rollback, dry-run=$dry_run, force=$force"
if [[ $dry_run == true ]]; then
log_info "Dry run completed successfully"
return 0
fi
if [[ $rollback == true ]]; then
deploy_rollback "/backups" "/var/www/app"
return $?
fi
# Normal deployment flow
deploy_preflight_check "$environment" "$version" || return 1
deploy_backup "/backups" "/var/www/app" || return 1
deploy_pull_code "git@github.com:company/app.git" "main" "/var/www/app" || return 1
deploy_install_deps "/var/www/app" || return 1
deploy_run_migrations "/var/www/app" "$environment" || return 1
deploy_restart_services "app.service" "nginx.service" || return 1
deploy_health_check "http://localhost:8080/health" || return 1
log_info "Deployment completed successfully: $version to $environment"
return 0
}
Part 5: Sourcing and Modular Scripts
Creating Function Libraries
#!/bin/bash
# main.sh - Main script using function libraries
set -euo pipefail
# Source function libraries
LIB_DIR="$(dirname "$0")/lib"
# Load all library files
for lib_file in "$LIB_DIR"/*.sh; do
if [[ -f $lib_file ]]; then
source "$lib_file"
log_debug "Loaded library: $(basename "$lib_file")"
fi
done
# Or load specific libraries
source "$LIB_DIR/lib-utils.sh"
source "$LIB_DIR/lib-deploy.sh"
source "$LIB_DIR/lib-monitoring.sh"
# Configuration
CONFIG_FILE="${CONFIG_FILE:-config.sh}"
if [[ -f $CONFIG_FILE ]]; then
source "$CONFIG_FILE"
fi
# Main function
main() {
local command=${1:-"help"}
case $command in
deploy)
local environment=${2:-"staging"}
local version=${3:-"latest"}
shift 3
deploy_application "$environment" "$version" "$@"
;;
backup)
local target=${2:-"/var/www/app"}
local destination=${3:-"/backups"}
deploy_backup "$destination" "$target"
;;
monitor)
check_port "localhost" 8080
get_system_load
get_memory_usage
;;
health)
deploy_health_check "http://localhost:8080/health"
;;
help|*)
echo "Usage: $0 {deploy|backup|monitor|health|help}"
echo ""
echo "Commands:"
echo " deploy Deploy application"
echo " backup [target] [dest] Create backup"
echo " monitor Check system health"
echo " health Check application health"
echo " help Show this help"
;;
esac
}
# Only run main if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Configuration File Example
#!/bin/bash
# config.sh - Configuration file
# Application settings
APP_NAME="myapp"
APP_VERSION="v1.2.3"
APP_DIR="/var/www/app"
# Database settings
DB_HOST="localhost"
DB_PORT="5432"
DB_NAME="app_db"
DB_USER="app_user"
# Deployment settings
DEPLOY_ENVIRONMENT="production"
BACKUP_DIR="/backups"
LOG_DIR="/var/log/app"
# Service settings
SERVICES=("app.service" "nginx.service" "postgresql.service")
# Network settings
API_PORT="8080"
HEALTH_CHECK_URL="http://localhost:8080/health"
# Monitoring thresholds
CPU_THRESHOLD=80
MEMORY_THRESHOLD=90
DISK_THRESHOLD=85
# Logging
LOG_LEVEL="INFO"
DEBUG=false
# Export variables for use in functions
export APP_NAME APP_VERSION APP_DIR
export DB_HOST DB_PORT DB_NAME DB_USER
export DEPLOY_ENVIRONMENT BACKUP_DIR LOG_DIR
export SERVICES API_PORT HEALTH_CHECK_URL
export CPU_THRESHOLD MEMORY_THRESHOLD DISK_THRESHOLD
export LOG_LEVEL DEBUG
Best Practices Checklist
local for all function variablesset -euo pipefail for error handlingdeclare -f to check if function exists1. Not using
local for function variables2. Modifying global variables unintentionally
3. Not validating input arguments
4. Returning strings via
return instead of echo5. Using
exit instead of return in functions6. Creating functions with side effects
7. Not handling errors within functions
8. Functions that are too long/complex
• Use
trap for cleanup in functions• Create decorator/wrapper functions
• Implement function composition
• Use
eval for dynamic function calls (carefully!)• Create self-documenting functions with
declare -f• Implement memoization for expensive function calls
• Use
typeset -f to list all defined functions
Function-Driven DevOps
Functions transform Bash from a simple scripting language into a powerful automation tool. By organizing your code into reusable functions, you create a library of DevOps utilities that can be shared across teams and projects.
Remember: Start by identifying repeated patterns in your scripts. Extract them into functions. Build libraries. Share them. Soon you'll have a powerful toolbox that makes complex automations simple.
Practice Exercise: Take an existing script you use and refactor it into functions. Create separate library files for utilities, deployment functions, and monitoring functions. Make the main script a simple orchestrator that calls these functions.
Define:
function name() { commands; }Call:
name arg1 arg2Arguments:
$1, $2, $@, $#Local vars:
local var="value"Return:
return 0 (exit code)Output:
echo "value" (capture with $())Source:
source lib.shCheck:
declare -f function_name