AWS Linux Tutorial -- Part 8: Bash Scripting for AWS

By Suraj Ahir 2025-10-29 11 min read

← Part 7AWS Linux Tutorial · Part 8 of 12Part 9 →
AWS Linux Tutorial -- Part 8: Bash Scripting for AWS

The AWS CLI is powerful on its own, but combined with bash scripting, it becomes a complete automation platform. You can launch and configure EC2 instances programmatically, automate backups to S3, deploy applications, send alerts on failures, and build entire infrastructure workflows that run on schedule.

AWS CLI Query with --query

Filter and format AWS CLI output
# Get only running instances
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query "Reservations[*].Instances[*].[InstanceId,PublicIpAddress,Tags[?Key==Name].Value | [0]]" \
  --output table

# Get instance IDs only
aws ec2 describe-instances \
  --query "Reservations[*].Instances[*].InstanceId" \
  --output text

# Get latest AMI ID for Ubuntu 22.04
aws ec2 describe-images \
  --owners 099720109477 \
  --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
  --query "sort_by(Images, &CreationDate)[-1].ImageId" \
  --output text

Launch EC2 from Script

Automated EC2 provisioning
#!/bin/bash
set -euo pipefail

AMI_ID="ami-0287a05f0ef0e9d9a"  # Ubuntu 22.04 ap-south-1
INSTANCE_TYPE="t3.micro"
KEY_NAME="my-key"
SECURITY_GROUP="sg-0123456789abcdef"
SUBNET_ID="subnet-0123456789abcdef"

echo "Launching EC2 instance..."
INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID \
  --instance-type $INSTANCE_TYPE \
  --key-name $KEY_NAME \
  --security-group-ids $SECURITY_GROUP \
  --subnet-id $SUBNET_ID \
  --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=auto-server}]" \
  --query "Instances[0].InstanceId" \
  --output text)

echo "Instance: $INSTANCE_ID"
echo "Waiting for running state..."
aws ec2 wait instance-running --instance-ids $INSTANCE_ID

PUBLIC_IP=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query "Reservations[0].Instances[0].PublicIpAddress" \
  --output text)

echo "Ready! SSH: ssh -i my-key.pem ubuntu@$PUBLIC_IP"

Deploy Application Script

Automated deployment from S3
#!/bin/bash
# deploy.sh -- Download and deploy app from S3
set -e

APP_NAME="myapp"
S3_BUCKET="my-deployments"
VERSION=${1:-latest}

log() { echo "[$(date +%H:%M:%S)] $1"; }

log "Deploying $APP_NAME version $VERSION"

# Download from S3
aws s3 cp "s3://${S3_BUCKET}/${APP_NAME}-${VERSION}.tar.gz" /tmp/

# Extract
tar -xzf "/tmp/${APP_NAME}-${VERSION}.tar.gz" -C /opt/

# Install dependencies
cd /opt/$APP_NAME
pip3 install -r requirements.txt

# Restart service
sudo systemctl restart $APP_NAME

# Health check
for i in $(seq 1 5); do
    curl -sf http://localhost:8000/health && { log "Healthy!"; exit 0; }
    log "Attempt $i failed, waiting..."
    sleep 10
done

log "ERROR: Health check failed after deployment"
exit 1

Frequently Asked Questions

How do I pass AWS credentials to scripts?

Preferred: Use IAM roles for EC2 -- no credentials in scripts. Alternative: aws configure sets credentials in ~/.aws/credentials. For CI/CD: use OIDC to assume roles, not stored keys. Never hardcode credentials in scripts or commit them to Git.

How do I handle errors in AWS bash scripts?

set -e exits on any error. set -u exits on undefined variables. set -o pipefail makes pipes fail properly. Use if aws command; then ... else ... fi for expected failures. Log errors to CloudWatch Logs for visibility.

How do I wait for an EC2 instance to be ready?

aws ec2 wait instance-running --instance-ids $ID waits until running state. aws ec2 wait instance-status-ok waits until all status checks pass. For SSH readiness: loop with ssh -o ConnectTimeout=5 until it succeeds.

How do I tag resources from bash scripts?

Pass --tag-specifications at creation time. Or use: aws ec2 create-tags --resources $ID --tags Key=Environment,Value=production Key=Project,Value=myapp. Always tag resources with environment, project, and owner for cost allocation and cleanup.

How do I send notifications on script success or failure?

Use AWS SNS: aws sns publish --topic-arn arn:aws:sns:... --message "Deploy done". Or Slack webhook: curl -X POST -d {"text":"Deploy complete"} $SLACK_WEBHOOK. Set up CloudWatch Alarms for automated monitoring alerts.

In Part 9, we master the AWS CLI with advanced queries, output formats, and profiles.

Key takeaways

Continue reading
Part 9 — Lambda and Serverless
Code without servers.
Suraj Ahir — author of SRJahir Tech

Written by

Suraj Ahir

Cloud & DevOps engineer running four live production services on my own AWS infrastructure. I write everything on this site myself — no ghostwriters, no AI filler.

← Part 7AWS Linux Tutorial · Part 8 of 12Part 9 →
← Back to Blog
Disclaimer: Educational content only.

Real Automation Scripts Used in Production

EC2 snapshot automation script
#!/bin/bash
# Create EBS snapshots for all production instances
set -euo pipefail

RETENTION_DAYS=7
ENVIRONMENT="production"

log() { echo "[$(date +%Y-%m-%dT%H:%M:%S)] $1"; }

# Get all production instance IDs
INSTANCES=$(aws ec2 describe-instances   --filters "Name=tag:Environment,Values=${ENVIRONMENT}"              "Name=instance-state-name,Values=running"   --query "Reservations[*].Instances[*].InstanceId"   --output text)

for INSTANCE_ID in $INSTANCES; do
    INSTANCE_NAME=$(aws ec2 describe-instances       --instance-ids $INSTANCE_ID       --query "Reservations[0].Instances[0].Tags[?Key=='Name'].Value | [0]"       --output text)
    
    # Get volumes attached to instance
    VOLUMES=$(aws ec2 describe-volumes       --filters "Name=attachment.instance-id,Values=${INSTANCE_ID}"       --query "Volumes[*].VolumeId" --output text)
    
    for VOLUME_ID in $VOLUMES; do
        SNAP_ID=$(aws ec2 create-snapshot           --volume-id $VOLUME_ID           --description "Automated backup of ${INSTANCE_NAME} ${INSTANCE_ID}"           --tag-specifications "ResourceType=snapshot,Tags=[
            {Key=Name,Value=auto-${INSTANCE_NAME}-$(date +%Y%m%d)},
            {Key=CreatedBy,Value=automation},
            {Key=RetentionDays,Value=${RETENTION_DAYS}}
          ]"           --query "SnapshotId" --output text)
        log "Created snapshot ${SNAP_ID} for volume ${VOLUME_ID} (${INSTANCE_NAME})"
    done
done

# Clean up old snapshots older than RETENTION_DAYS
OLD_SNAPS=$(aws ec2 describe-snapshots   --filters "Name=tag:CreatedBy,Values=automation"   --query "Snapshots[?StartTime<='$(date -d "-${RETENTION_DAYS} days" +%Y-%m-%dT%H:%M:%S)'].SnapshotId"   --output text)

for SNAP_ID in $OLD_SNAPS; do
    aws ec2 delete-snapshot --snapshot-id $SNAP_ID
    log "Deleted old snapshot: ${SNAP_ID}"
done

log "Backup automation complete"

Automated Cost Report Script

Daily AWS cost summary via email
#!/bin/bash
# Send daily AWS cost summary

YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
TODAY=$(date +%Y-%m-%d)

COST=$(aws ce get-cost-and-usage   --time-period Start=$YESTERDAY,End=$TODAY   --granularity DAILY   --metrics BlendedCost   --query "ResultsByTime[0].Total.BlendedCost.Amount"   --output text)

COST_ROUNDED=$(printf "%.2f" $COST)

# Send via SES
aws ses send-email   --from "alerts@mycompany.com"   --to "team@mycompany.com"   --subject "AWS Daily Cost: \$${COST_ROUNDED} for $YESTERDAY"   --text "AWS spend for $YESTERDAY: \$${COST_ROUNDED} USD"   --region us-east-1

AWS CLI Pagination and Large Result Sets

Handle results with thousands of items
#!/bin/bash
# Process ALL EC2 instances across all regions

REGIONS=$(aws ec2 describe-regions --query "Regions[*].RegionName" --output text)

for REGION in $REGIONS; do
    echo "=== Region: $REGION ==="
    
    # --paginate handles multiple pages automatically
    aws ec2 describe-instances         --region $REGION         --query "Reservations[*].Instances[*].[InstanceId,State.Name,InstanceType,Tags[?Key=='Name'].Value|[0]]"         --output text 2>/dev/null
done

Parallel AWS Operations

Run operations on multiple resources simultaneously
#!/bin/bash
# Tag 100 instances in parallel (much faster than sequential)

INSTANCES=$(aws ec2 describe-instances   --filters "Name=tag:Environment,Values=untagged"   --query "Reservations[*].Instances[*].InstanceId"   --output text)

# Run 10 tagging operations in parallel using GNU parallel
echo $INSTANCES | tr ' ' '
' | parallel -j10   aws ec2 create-tags --resources {} --tags Key=Project,Value=myapp

# Alternative: xargs with -P for parallel
echo $INSTANCES | tr ' ' '
' | xargs -P10 -I{}   aws ec2 create-tags --resources {} --tags Key=Project,Value=myapp