Bash is the lingua franca of server automation. Every DevOps engineer needs to write scripts that deploy code, rotate logs, check health endpoints, and automate repetitive tasks. This guide covers the building blocks.
Script Basics
Always start with a shebang and enable strict mode:
#!/usr/bin/env bash
set -euo pipefail
# -e: exit on error
# -u: treat unset variables as errors
# -o pipefail: catch errors in piped commands
Make scripts executable:
chmod +x deploy.sh
./deploy.sh
Variables and Arguments
# Variables
APP_NAME="my-api"
DEPLOY_DIR="/opt/${APP_NAME}"
VERSION=${1:-"latest"} # First argument, default "latest"
echo "Deploying ${APP_NAME} version ${VERSION} to ${DEPLOY_DIR}"
# Special variables
echo "Script name: $0"
echo "Argument count: $#"
echo "All arguments: $@"
echo "Last exit code: $?"
echo "Process ID: $$"
Conditionals
# File tests
if [[ -f /etc/nginx/nginx.conf ]]; then
echo "Nginx config exists"
fi
if [[ -d /var/log/app ]]; then
echo "Log directory exists"
fi
# String comparison
if [[ "$ENV" == "production" ]]; then
echo "Running in production mode"
fi
# Numeric comparison
DISK_USAGE=$(df / --output=pcent | tail -1 | tr -d ' %')
if (( DISK_USAGE > 80 )); then
echo "WARNING: Disk usage is ${DISK_USAGE}%"
fi
# Command success
if docker ps | grep -q "my-container"; then
echo "Container is running"
else
echo "Container is not running, starting..."
docker start my-container
fi
Loops
# Iterate over servers
SERVERS=("web1.example.com" "web2.example.com" "web3.example.com")
for server in "${SERVERS[@]}"; do
echo "Deploying to ${server}..."
ssh deploy@"${server}" "cd /opt/app && git pull && docker compose up -d"
done
# Retry loop with backoff
MAX_RETRIES=5
for i in $(seq 1 $MAX_RETRIES); do
if curl -sf http://localhost:3000/health > /dev/null; then
echo "Health check passed"
break
fi
echo "Attempt $i/$MAX_RETRIES failed, retrying in ${i}s..."
sleep "$i"
done
Functions
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*"
}
check_dependency() {
if ! command -v "$1" &> /dev/null; then
log "ERROR" "$1 is required but not installed"
exit 1
fi
}
# Usage
check_dependency docker
check_dependency git
log "INFO" "All dependencies satisfied"
Error Handling
# Trap errors and clean up
cleanup() {
log "INFO" "Cleaning up temporary files..."
rm -rf "${TEMP_DIR}"
}
trap cleanup EXIT
TEMP_DIR=$(mktemp -d)
log "INFO" "Working in ${TEMP_DIR}"
# Custom error handler
on_error() {
log "ERROR" "Script failed at line $1"
# Send notification
curl -s -X POST "https://hooks.slack.com/..." \
-d '{"text":"Deploy script failed at line '"$1"'"}'
}
trap 'on_error $LINENO' ERR
Cron Jobs
Automate script execution with cron:
# Edit crontab
crontab -e
# Run backup every day at 2 AM
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# Health check every 5 minutes
*/5 * * * * /opt/scripts/healthcheck.sh
# Log rotation every Sunday at midnight
0 0 * * 0 /opt/scripts/rotate-logs.sh
Useful One-Liners
# Watch Docker container resource usage
watch docker stats --no-stream
# Find files larger than 100MB
find / -type f -size +100M -exec ls -lh {} + 2>/dev/null
# Tail multiple log files simultaneously
tail -f /var/log/nginx/access.log /var/log/app/app.log
# Quick HTTP server for file sharing
python3 -m http.server 8080
# Generate a random password
openssl rand -base64 32
# Check SSL certificate expiry
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate
# Kill all processes matching a pattern
pkill -f "node server.js"
# Parallel SSH to multiple servers
parallel-ssh -i -h servers.txt "uptime"
Script Template
#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} [options]
Options:
-e, --env ENV Target environment (staging|production)
-v, --version VER Version to deploy
-h, --help Show this help
EOF
}
main() {
local env="staging"
local version="latest"
while [[ $# -gt 0 ]]; do
case "$1" in
-e|--env) env="$2"; shift 2 ;;
-v|--version) version="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) log "Unknown option: $1"; usage; exit 1 ;;
esac
done
log "Deploying version ${version} to ${env}"
# Your logic here
}
main "$@"
Master these patterns and you can automate virtually any server task. Start simple, add error handling, and evolve scripts into roles or modules as complexity grows.
Tagged with
Enjoyed this article?
Get more DevOps insights delivered to your inbox.
Get new posts by email
Subscribe to get an email when a new blog post is published. Skip anytime.
No spam, unsubscribe anytime.
Related Posts
Discussion
0 comments
Sign in to join the conversation.
Be the first to comment
Start a conversation about this post
