Script Basics and Variables
Script Basics and Variables
Shebang and Basic Structure
#!/bin/bash
set -euo pipefail # Strict mode: exit on error, undefined vars, pipe failures
# Your script content here
echo "Hello, $USER"
set -euo pipefail explained:
-e: Exit immediately if a command exits with non-zero status-u: Treat unset variables as errors-o pipefail: A pipeline fails if any command fails (not just the last)
Variables
# Assigning variables (NO spaces around =)
NAME="production"
ENVIRONMENT="staging"
PORT=8080
# Using variables
echo "Deploying to $NAME"
echo "Environment: ${ENVIRONMENT}"
echo "Port: $PORT"
# String concatenation
FULL_NAME="${ENVIRONMENT}-${NAME}"
echo "Full: $FULL_NAME"
# Default values
echo "${DB_HOST:-localhost}" # Use 'localhost' if DB_HOST is unset
echo "${DB_PORT:=5432}" # Set to 5432 if unset
echo "${CONFIG_FILE:?Config not set}" # Exit with error if unset
# Command substitution
echo "Today is $(date +%Y-%m-%d)"
TIMESTAMP=$(date +%s)
LINE_COUNT=$(wc -l < /etc/passwd)
echo "Password file has $LINE_COUNT lines"
# Arithmetic expansion
COUNT=5
NEXT=$((COUNT + 1))
echo "Next: $NEXT"
# Arrays
declare -a SERVERS=("web1" "web2" "web3")
echo "First server: ${SERVERS[0]}"
echo "All servers: ${SERVERS[@]}"
echo "Number of servers: ${#SERVERS[@]}"
# Iterate over array
for server in "${SERVERS[@]}"; do
echo "Pinging $server..."
ping -c 1 -W 1 "$server" &>/dev/null && echo "$server is up" || echo "$server is down"
done
Conditionals and Control Flow
Conditionals and Control Flow
if/elif/else
#!/bin/bash
set -euo pipefail
ENV="production"
# Basic if/else
if [ "$ENV" = "production" ]; then
echo "Running in production mode"
elif [ "$ENV" = "staging" ]; then
echo "Running in staging mode"
else
echo "Running in development mode"
fi
# Test conditions
FILE="/etc/nginx/nginx.conf"
if [ -f "$FILE" ]; then
echo "$FILE exists and is a regular file"
fi
if [ -d "/var/log" ]; then
echo "/var/log is a directory"
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable"
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable"
fi
if [ -x "/usr/bin/docker" ]; then
echo "Docker is installed"
fi
if [ -s "$FILE" ]; then
echo "$FILE has content (non-empty)"
fi
# String comparisons
if [ "$ENV" != "development" ]; then
echo "Not in development"
fi
if [ -z "$EMPTY_VAR" ]; then
echo "Variable is empty or unset"
fi
if [ -n "$ENV" ]; then
echo "Variable is set and non-empty"
fi
# Numeric comparisons
PORT=8080
if [ "$PORT" -gt 1024 ]; then
echo "Non-privileged port"
fi
# -eq equal
# -ne not equal
# -gt greater than
# -ge greater or equal
# -lt less than
# -le less or equal
# Logical operators
if [ "$ENV" = "production" ] && [ -f "/etc/nginx/nginx.conf" ]; then
echo "Production with nginx"
fi
if [ "$ENV" = "development" ] || [ "$ENV" = "staging" ]; then
echo "Non-production environment"
fi
# Case statement
DEPLOY_TARGET="api"
case "$DEPLOY_TARGET" in
api)
echo "Deploying API service"
;;
web)
echo "Deploying web frontend"
;;
worker)
echo "Deploying background worker"
;;
*)
echo "Unknown deploy target: $DEPLOY_TARGET"
exit 1
;;
esac
Loops and Iteration
Loops and Iteration
For Loops
#!/bin/bash
set -euo pipefail
# Iterate over a list
for env in dev staging production; do
echo "Deploying to $env"
# kubectl apply --env=$env ...
done
# Iterate over files
for file in /var/log/*.log; do
if [ -f "$file" ]; then
SIZE=$(du -h "$file" | cut -f1)
echo "$file: $SIZE"
fi
done
# C-style for loop
for ((i=1; i<=5; i++)); do
echo "Iteration $i"
done
# Iterate over numbers
for port in {8080..8090}; do
echo "Checking port $port..."
ss -tln | grep -q ":$port " && echo "Port $port in use" || echo "Port $port available"
done
# Iterate over command output
for user in $(grep '/bin/bash' /etc/passwd | cut -d: -f1); do
echo "User with bash: $user"
done
# Parallel execution with xargs
find /var/log -name '*.log' -mtime +30 | xargs -P 4 -I {} gzip {}
While Loops
# Read file line by line
while IFS= read -r line; do
echo "Processing: $line"
done < servers.txt
# Read command output line by line
kubectl get pods --all-namespaces | while read -r namespace pod status rest; do
if [ "$status" != "Running" ]; then
echo "Pod $pod in $namespace is $status"
fi
done
# Infinite loop with break
while true; do
if curl -sf http://localhost:8080/health > /dev/null 2>&1; then
echo "Service is ready"
break
fi
echo "Waiting for service..."
sleep 5
done
# Wait for condition with timeout
TIMEOUT=60
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
if docker inspect mycontainer > /dev/null 2>&1; then
echo "Container is running"
break
fi
sleep 1
ELAPSED=$((ELAPSED + 1))
done
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "Timeout waiting for container"
exit 1
fi
Loop Control
# break - exit the loop
for server in web1 web2 web3 web4; do
if ! ping -c 1 -W 1 "$server" &>/dev/null; then
echo "$server is down, stopping check"
break
fi
echo "$server is up"
done
# continue - skip to next iteration
for file in /tmp/*.tmp; do
[ -f "$file" ] || continue # Skip if not a file
echo "Processing $file"
done
# Nested loops with labels
OUTER:
for i in {1..3}; do
for j in {1..3}; do
if [ $((i * j)) -gt 4 ]; then
echo "Breaking outer at i=$i, j=$j"
break OUTER
fi
echo "i=$i, j=$j, product=$((i * j))"
done
done
Functions and Error Handling
Functions and Error Handling
Functions
#!/bin/bash
set -euo pipefail
# Function definition
check_service() {
local host="$1"
local port="$2"
local timeout="${3:-5}"
if timeout "$timeout" bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
echo "Service $host:$port is UP"
return 0
else
echo "Service $host:$port is DOWN"
return 1
fi
}
# Call function
check_service "localhost" "8080"
check_service "db.example.com" "5432" 10
# Function with output capture
get_container_ip() {
local container_name="$1"
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container_name" 2>/dev/null
}
IP=$(get_container_ip "myapp")
echo "Container IP: $IP"
# Return values vs output
# Use 'return' for status codes (0-255)
# Use 'echo' for data output
# Use 'printf' for complex output
Error Handling
#!/bin/bash
set -euo pipefail
# Trap errors
trap 'echo "ERROR on line $LINENO. Exit code: $?"' ERR
# Trap exit (cleanup)
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
echo "Working in $TMPDIR"
# Retry logic
retry() {
local max_attempts=$1
local delay=$2
shift 2
local cmd=("$@")
local attempt=1
while [ $attempt -le $max_attempts ]; do
if "${cmd[@]}"; then
return 0
fi
echo "Attempt $attempt failed. Retrying in ${delay}s..."
sleep "$delay"
attempt=$((attempt + 1))
done
echo "All $max_attempts attempts failed"
return 1
}
# Usage
retry 3 5 curl -sf http://localhost:8080/health
# Logging functions
log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $*"; }
log_warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN: $*" >&2; }
log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; }
log_info "Deployment started"
log_warn "Disk usage at 80%"
log_error "Service failed to start"
# Validate inputs
validate_port() {
local port="$1"
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
log_error "Invalid port: $port"
return 1
fi
return 0
}
Command-Line Arguments
#!/bin/bash
set -euo pipefail
# Parse named arguments
while [[ $# -gt 0 ]]; do
case $1 in
-e|--env)
ENV="$2"
shift 2
;;
-p|--port)
PORT="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 -e <env> -p <port>"
echo " -e, --env Environment (dev|staging|prod)"
echo " -p, --port Port number"
exit 0
;;
*)
log_error "Unknown option: $1"
exit 1
;;
esac
done
# Set defaults
ENV="${ENV:-development}"
PORT="${PORT:-8080}"
echo "Environment: $ENV, Port: $PORT"
Complete DevOps Script Example
#!/bin/bash
set -euo pipefail
log_info() { echo "[$(date '+%H:%M:%S')] INFO: $*"; }
log_error() { echo "[$(date '+%H:%M:%S')] ERROR: $*" >&2; }
# Configuration
APP_NAME="myapp"
DEPLOY_ENV="${1:-staging}"
HEALTH_URL="http://localhost:8080/health"
# Deploy function
deploy() {
log_info "Building $APP_NAME for $DEPLOY_ENV..."
docker build -t "$APP_NAME:$DEPLOY_ENV" .
log_info "Stopping old container..."
docker stop "$APP_NAME" 2>/dev/null || true
docker rm "$APP_NAME" 2>/dev/null || true
log_info "Starting new container..."
docker run -d --name "$APP_NAME" -p 8080:8080 "$APP_NAME:$DEPLOY_ENV"
log_info "Waiting for health check..."
local retries=30
while [ $retries -gt 0 ]; do
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
log_info "Deployment successful!"
return 0
fi
retries=$((retries - 1))
sleep 2
done
log_error "Health check failed after 60s"
docker logs "$APP_NAME" | tail -20
return 1
}
# Rollback function
rollback() {
log_error "Rolling back..."
docker stop "$APP_NAME" 2>/dev/null || true
docker rm "$APP_NAME" 2>/dev/null || true
docker run -d --name "$APP_NAME" -p 8080:8080 "$APP_NAME:previous"
}
trap rollback ERR
# Execute
deploy
log_info "Deploy complete"