Write professional Bash scripts for automation. Learn functions, error handling, argument parsing, logging, parallel execution, and common scripting patterns.
Advanced Bash Scripting
Script Template
#!/usr/bin/env bash
# Script: deploy.sh
# Description: Automated deployment script
# Usage: ./deploy.sh [options] <environment>
set -euo pipefail # Exit on error, undefined var, pipe failure
IFS=#39;
' # Safer word splitting
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
# Defaults
LOG_FILE="${SCRIPT_DIR}/deploy.log"
DRY_RUN=false
ENVIRONMENT=""
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Logging
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
info() { log "${GREEN}[INFO]${NC} $*"; }
warn() { log "${YELLOW}[WARN]${NC} $*"; }
error() { log "${RED}[ERROR]${NC} $*" >&2; }
die() { error "$@"; exit 1; }
Argument Parsing
usage() {
cat << EOF
Usage: $(basename "$0") [options] <environment>
Options:
-h, --help Show help
-n, --dry-run Dry run mode
-v, --verbose Verbose output
-t, --tag TAG Docker image tag
Environments:
staging, production
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage ;;
-n|--dry-run) DRY_RUN=true ;;
-v|--verbose) set -x ;;
-t|--tag)
[[ -n "${2:-}" ]] || die "Option $1 requires an argument"
TAG="$2"
shift 2
;;
--) shift; break ;;
-*) die "Unknown option: $1" ;;
*) ENVIRONMENT="$1"; shift ;;
esac
done
[[ -n "$ENVIRONMENT" ]] || die "Environment required"
[[ "$ENVIRONMENT" =~ ^(staging|production)$ ]] || die "Invalid environment: $ENVIRONMENT"
}
parse_args "$@"
Error Handling and Cleanup
# Trap for cleanup on exit
cleanup() {
local exit_code=$?
info "Cleaning up..."
rm -f /tmp/deploy-$-*
if [[ $exit_code -ne 0 ]]; then
error "Script failed with exit code $exit_code"
fi
exit $exit_code
}
trap cleanup EXIT INT TERM
# Retry function
retry() {
local max=$1
local delay=$2
local cmd="${@:3}"
local count=0
until $cmd; do
count=$((count + 1))
if [[ $count -ge $max ]]; then
error "Command failed after $max attempts: $cmd"
return 1
fi
warn "Attempt $count failed. Retrying in ${delay}s..."
sleep "$delay"
done
}
retry 3 5 curl -sf https://api.example.com/health
Functions and Return Values
# Return multiple values via global variables or stdout
get_version() {
local env="$1"
local version
version=$(git describe --tags --abbrev=0)
echo "$version" # Return via stdout
}
check_service() {
local url="$1"
local timeout="${2:-5}"
curl --silent --fail --max-time "$timeout" "$url" > /dev/null 2>&1
return $? # Return via exit code
}
# Usage
VERSION=$(get_version "$ENVIRONMENT")
if check_service "https://api.example.com/health"; then
info "Service is healthy"
else
die "Service health check failed"
fi
Parallel Execution
# Run tasks in parallel
deploy_services() {
local pids=()
for service in api worker scheduler; do
(
info "Deploying $service..."
kubectl rollout restart deployment/$service -n production
kubectl rollout status deployment/$service -n production --timeout=300s
) &
pids+=($!)
done
# Wait for all and check status
local failed=0
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
failed=$((failed + 1))
fi
done
return $failed
}
# GNU Parallel for more control
parallel --jobs 4 --halt now,fail=1 'echo "Processing {}" && process_item {}' ::: item1 item2 item3 item4
Arrays and Associative Arrays
# Indexed arrays
services=("api" "worker" "scheduler" "cron")
# Loop
for service in "${services[@]}"; do
echo "Service: $service"
done
# Slice
echo "${services[@]:1:2}" # worker scheduler
# Associative arrays
declare -A config
config[host]="db.example.com"
config[port]="5432"
config[db]="myapp"
# Loop over keys
for key in "${!config[@]}"; do
echo "$key=${config[$key]}"
done
Text Processing
# String manipulation
name="hello-world"
echo "${name^^}" # HELLO-WORLD (uppercase)
echo "${name^}" # Hello-world (capitalize)
echo "${name//-/_}" # hello_world (replace)
echo "${name#hello-}" # world (remove prefix)
echo "${name%%-*}" # hello (remove suffix)
echo "${name:6:5}" # world (substring)
# Process substitution
diff <(sort file1.txt) <(sort file2.txt)
# Here documents
read -r -d '' SQL << 'EOF'
SELECT id, name, email
FROM users
WHERE created_at > NOW() - INTERVAL '7 days'
EOF
# Efficient text processing
grep -r "TODO" --include="*.ts" --exclude-dir=node_modules | wc -l
find . -name "*.log" -mtime +30 -exec rm {} ;
Script Best Practices
| Practice |
Why |
set -euo pipefail |
Fail fast on errors |
shellcheck |
Static analysis |
| Quote variables |
Prevent word splitting |
readonly for constants |
Prevent accidental changes |
| Log with timestamps |
Easier debugging |
| Cleanup with traps |
No leftover temp files |