Build an AWS S3 Toolkit with Bash

Write three Bash scripts that create, report on, and clean up S3 buckets.

Introduction

30 Second Summary

Every time you need to create, check on, or delete something in the cloud, you have two choices: click through a web console one step at a time, or write a script that handles it all in a single command.

In this project, you will build three Bash scripts that automate real AWS S3 operations from the command line. Each script handles a different job: creating buckets, reporting on your resources, and safely cleaning everything up.

What You'll Build

Picture typing one command and watching your terminal create an S3 bucket on AWS, upload files into it, and confirm everything is live.

By the end of this project, you'll have:

  • A management script you run to create an S3 bucket and upload files in one command.
  • An inventory report script that loops through every bucket in your account and displays object counts in a formatted table.
  • A cleanup script with pattern matching and a confirmation prompt that safely deletes only the buckets you created.
  • Secret Mission: Add a --dry-run safety mode to your cleanup script so you can preview deletions without making any changes.

Do I need anything before I start?

You will need an AWS account with IAM user credentials (an Access Key ID and Secret Access Key) and WSL (Windows Subsystem for Linux) installed on your Windows machine. The project walks you through installing the AWS CLI and everything else from there.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Bash Environment and AWS CLI

Your three scripts will talk to AWS from the command line. Before you can automate anything, you need a working Bash shell with the AWS CLI installed and connected to your AWS account.

In this step, you will install AWS CLI v2 inside WSL, configure it with your IAM credentials, and create your project folder.

In this step, get ready to:
  • Install AWS CLI v2 inside WSL.
  • Configure the CLI with your AWS credentials, default region, and output format.
  • Create the project directory and verify everything works.
Open WSL and install AWS CLI v2

WSL gives you a full Linux environment on Windows. The AWS CLI runs natively inside it, and every Bash feature your scripts need (arrays, pattern matching, functions) works without compatibility issues.

  • Press the Windows key to open your search bar.
  • Type Ubuntu and press Enter to open your WSL terminal.

Installed a different Linux distribution?

If you installed a distribution other than Ubuntu (like Debian or Fedora), search for its name instead. The rest of the instructions work the same.

  • Check if AWS CLI is already installed by running:
aws --version

✔️ I see aws-cli/2 in the output

AWS CLI v2 is already installed. Move on to the next substep.

ⓧ I see aws-cli/1 in the output

You have an older version of the AWS CLI. This project needs v2. Install it by running these commands:

curl -fsSL https://awscli.amazonaws.com/v2/install.sh | bash
source ~/.profile

What do these commands do?

The first command downloads and runs the official AWS install script. It places CLI v2 in ~/.local/bin, which takes priority over the older version on your PATH.

The second command reloads your shell profile so the terminal can find the new aws command immediately.

  • Verify the upgrade succeeded by running:
aws --version

You should now see aws-cli/2 at the start of the output.

Still seeing aws-cli/1?

The old version may still be first on your PATH. Try closing and reopening your WSL terminal, then run aws --version again.

If it still shows v1, run which aws to see which binary is being used. The v2 binary should be at ~/.local/bin/aws.

Still stuck? Help me get AWS CLI v2 working in WSL.

ⓧ Command not found

AWS CLI is not installed yet. Install it by running these commands:

curl -fsSL https://awscli.amazonaws.com/v2/install.sh | bash
source ~/.profile

What do these commands do?

The first command downloads and runs the official AWS install script. It installs the CLI to ~/.local/share/aws-cli and creates a shortcut in ~/.local/bin.

The second command reloads your shell profile so the terminal can find the new aws command immediately.

  • Verify the installation succeeded by running:
aws --version

You should see output starting with aws-cli/2 followed by a version number.

Still seeing command not found?

Try closing and reopening your WSL terminal, then run aws --version again. This forces your shell to reload its PATH.

If that does not work, check that ~/.local/bin is on your PATH by running echo $PATH. If you do not see it listed, add it manually by running:

export PATH="$HOME/.local/bin:$PATH"

Still stuck? Help me install AWS CLI v2 in WSL.

Configure your AWS credentials

The CLI needs to know which AWS account to connect to. You will link it to your IAM user by providing your Access Key ID and Secret Access Key.

What will aws configure ask?

The command will prompt you for four values, one at a time. Have your IAM credentials ready before you begin. You can find them in your IAM user's security credentials page in the AWS Console.

  • Connect your CLI to AWS by running:
aws configure
  • Paste your AWS Access Key ID when prompted and press Enter.
  • Paste your AWS Secret Access Key when prompted and press Enter.
  • Type us-east-1 for the Default region name and press Enter.
  • Type json for the Default output format and press Enter.

Where are my credentials stored?

Your Access Key and Secret Key are saved in ~/.aws/credentials. Your region and output format are saved in ~/.aws/config. Every AWS CLI command you run from now on uses these values automatically.

Not sure where to find your IAM credentials?

Sign in to the AWS Console, open the IAM service, and select Users. Click your username, then open the Security credentials tab. You can create a new access key from there.

Need more help? Walk me through finding my IAM access keys.

Create the project directory and verify your setup

All three scripts you build in this project will live in a single folder. Creating it now gives you a clean workspace.

  • Create your project folder and move into it by running these commands:
mkdir ~/s3-toolkit
cd ~/s3-toolkit
  • Confirm you are in the right place by running:
pwd

You should see /home/your-username/s3-toolkit printed to the terminal.

Now for the real test. Before you run the next command, what do you think AWS will send back to prove your CLI is connected? What information would it need to confirm about your identity?

  • Test your AWS connection by running:
aws sts get-caller-identity

You should see a JSON response containing your AWS account ID, user ARN, and user ID.

What does this output mean?

  • Account is your 12-digit AWS account number.
  • Arn is the Amazon Resource Name that uniquely identifies your IAM user.
  • UserId is the internal identifier AWS assigned to your user.

If you see all three fields, your CLI is fully connected and ready to manage AWS resources.

Seeing an authentication error?

Double check that you pasted your Access Key ID and Secret Access Key correctly during aws configure. A single extra space or missing character will cause authentication to fail. Run aws configure again to re-enter them.

Still stuck? Help me fix my AWS CLI authentication error.

  • Confirm your Bash version by running:
bash --version

You should see version information starting with a version number. Any modern version of Bash will work for this project.

Your environment is locked and loaded. You have a Bash shell, a configured AWS CLI, and a project folder ready to go. Next up, you will write your first script and watch it create a real S3 bucket on AWS.

Write Your First S3 Automation Script

Your Bash environment is ready and connected to AWS. Now it's time to put it to work by writing your first script.

Typing AWS CLI commands one at a time is slow and error-prone. A Bash script packages multiple commands into a single reusable file. In this step, you will create a script that creates an S3 bucket and uploads files to it with one command.

In this step, get ready to:
  • Create a Bash script that automates S3 bucket creation and file uploads.
  • Make the script executable and run it.
  • Verify the bucket and files exist on AWS.
Create the s3-manage.sh script

Your script will store the bucket name and region in variables, then call AWS CLI commands to create the bucket and upload files. You will write it using nano, a text editor that runs right inside your terminal.

  • Return to your ~/s3-toolkit directory by running this command:
cd ~/s3-toolkit
  • Open nano to create a new file called s3-manage.sh by running:
nano s3-manage.sh

What is nano?

nano is a beginner-friendly text editor that runs in the terminal. When it opens, you will see a blank editing area with shortcut hints at the bottom (like ^O for save and ^X for exit). The ^ symbol means the Ctrl key.

  • Type (or paste) the following code into nano to set up variables and create the S3 bucket:
#!/bin/bash

# Set up variables for the bucket name and region
BUCKET_NAME="my-bash-project-$(date +%s)"
REGION="us-east-1"

# Print a header with the script's configuration
echo "=== S3 Management Script ==="
echo "Bucket name: $BUCKET_NAME"
echo "Region: $REGION"
echo ""

# Create the S3 bucket using the AWS CLI
echo "Creating S3 bucket..."
aws s3 mb "s3://$BUCKET_NAME" --region "$REGION"

What does this code do?

  • #!/bin/bash is the shebang line. It tells the system to run this file using the Bash interpreter.
  • BUCKET_NAME uses $(date +%s) to append the current Unix timestamp to the name, making it globally unique every time you run the script.
  • aws s3 mb is the AWS CLI command to make a bucket. The --region flag sets where the bucket is created.
  • Leave a blank line after the last line, then continue typing the next section of the script below it:
# Create two sample text files in the current directory
echo "Creating sample files..."
echo "Project: AWS Bash Automation" > project-info.txt
echo "Author: Cloud Engineer" >> project-info.txt
echo "Date: $(date)" >> project-info.txt

echo "This is sample data for testing." > sample-data.txt
echo "Line 2: More test data" >> sample-data.txt
echo "Line 3: Even more test data" >> sample-data.txt

# Upload both files to the S3 bucket
echo "Uploading files to S3..."
aws s3 cp project-info.txt "s3://$BUCKET_NAME/"
aws s3 cp sample-data.txt "s3://$BUCKET_NAME/"

# List the bucket contents to confirm the upload
echo ""
echo "Listing bucket contents:"
aws s3 ls "s3://$BUCKET_NAME/"

echo ""
echo "Your bucket '$BUCKET_NAME' is live on AWS!"

What does this code do?

  • > writes to a file and overwrites anything already there. >> appends a new line to the end of the file without erasing the existing content.
  • aws s3 cp copies a local file to the S3 bucket. Each file is uploaded separately with its own command.
  • aws s3 ls lists the contents of the bucket so you can confirm both files arrived.
  • Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
  • Exit nano by pressing Ctrl+X.

✔️ Awesome, I've got everything!

Great. Make sure you have saved s3-manage.sh and exited nano before continuing.

ⓧ I'd like to double check the full code

Your s3-manage.sh should contain two sections in order:

  • Section 1 (the first code block above): the shebang line, BUCKET_NAME and REGION variables, the header echo statements, and the aws s3 mb command.
  • Section 2 (the second code block above): the file creation lines, the aws s3 cp upload commands, the aws s3 ls listing, and the final success message.

Scroll up and confirm each section is present, in order. If anything is missing, reopen the file with nano s3-manage.sh and correct it.

Make the script executable and run it

Bash scripts are just text files by default. You need to add execute permission before you can run one.

  • Make s3-manage.sh executable by running this command:
chmod +x s3-manage.sh

What does chmod +x do?

chmod changes file permissions. The +x flag adds execute permission, which lets you run the file as a program with ./ instead of just reading it as text.

Before you run the script, what do you think will happen? The script calls aws s3 mb to create a bucket, creates two local files, uploads them with aws s3 cp, and lists the bucket contents.

  • Run your script by executing:
./s3-manage.sh

You should see output confirming each step: the bucket name being created, files being uploaded, and a listing of the two files now in your S3 bucket.

What should I see?

Your terminal output will look something like this:

  • make_bucket: my-bash-project-1234567890 confirms the bucket was created.
  • upload: lines confirm each file was copied to S3.
  • The listing at the end shows the date, time, size, and name of each file in the bucket.

Take note of your bucket name from the output. You will need it in the next substep.

Seeing a permission denied or command not found error?

  • If you see Permission denied, double check that you ran chmod +x s3-manage.sh before trying to execute the script.
  • If you see command not found, make sure you are running ./s3-manage.sh (with the ./ prefix) from inside the ~/s3-toolkit directory.
  • If you see an AWS credentials error, run aws configure again and re-enter your Access Key ID and Secret Access Key.

Still stuck? Help me debug why my s3-manage.sh script is failing.

Verify the bucket and files on AWS

The script already listed the bucket contents at the end, but let's independently confirm the bucket exists on AWS.

  • Verify the bucket contents by running this command, replacing the bucket name with the one from your script's output:
aws s3 ls s3://[[S3_BUCKET_NAME="my-bash-project-1234567890"]]/

You should see project-info.txt and sample-data.txt listed with their dates and sizes. This confirms the script created real resources on AWS.

Seeing a NoSuchBucket error?

  • Make sure you copied the exact bucket name from the script output, including the timestamp at the end.
  • Do not forget the trailing / after the bucket name.

Need help? Help me verify that my S3 bucket was created successfully.

Your first automation script is working. You created an S3 bucket, uploaded files, and verified the results in a single command. Next up, you will build a report script that loops through every bucket in your account and displays a formatted inventory.

Build an S3 Inventory Report

Your management script does its job. You can create a bucket and upload files with a single command.

But what about the rest of your account? Clicking through the AWS Console bucket by bucket is slow. In this step, you'll write a report script that loops through every bucket and displays object counts in a formatted table.

In this step, get ready to:
  • Write an inventory report script with a for loop and printf formatting.
  • Create a second empty test bucket to give the report more data.
  • Run the report and observe how it handles different bucket states.
Start the report script with a header and bucket list

The first thing a report needs is a banner showing what it is and when it ran. After that, the script fetches every bucket name from your account using the AWS CLI.

  • Create s3-report.sh in nano by running:
nano s3-report.sh
  • Type this code into the editor:
#!/bin/bash

# Print report header
echo "========================================"
echo "       S3 Inventory Report"
echo "========================================"
echo "Generated: $(date)"
echo ""

# Fetch all bucket names from the account
BUCKETS=$(aws s3api list-buckets --query 'Buckets[].Name' --output text)

What does this code do?

  • #!/bin/bash is the shebang line. It tells the system to run the script with Bash.
  • The echo lines print a banner with the report title and the current date.
  • BUCKETS=$(aws s3api list-buckets ...) runs the AWS CLI command inside $() and stores the result in a variable. The --query flag uses JMESPath syntax to extract just the bucket names from the JSON response.
  • Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
  • Exit nano by pressing Ctrl+X.
  • Make the script executable and run it by running these commands:
chmod +x s3-report.sh
./s3-report.sh

You should see the report banner with the title, a row of equal signs, and the current date. The script also fetched your bucket names, but there is nothing to display them yet.

Seeing a permission denied error?

  • Make sure you ran chmod +x s3-report.sh before ./s3-report.sh. Without execute permission, the script cannot run.
  • Check that the shebang line #!/bin/bash is the very first line in the file with no blank lines above it.
  • Why is my s3-report.sh script not running?
Add the formatted table and loop

Now the script needs to do something with those bucket names. A for loop will iterate through each one, count its objects, and print a row in a neatly aligned table.

  • Open s3-report.sh again in nano by running:
nano s3-report.sh
  • Move your cursor to the end of the file, below the BUCKETS=... line.
  • Add this code below it:
# Print column headers
printf "  %-40s %s\n" "BUCKET NAME" "OBJECTS"
printf "  %-40s %s\n" "----------------------------------------" "-------"

TOTAL_BUCKETS=0

# Loop through each bucket and count objects
for BUCKET in $BUCKETS; do
    OBJECT_COUNT=$(aws s3api list-objects-v2 --bucket "$BUCKET" --query 'length(Contents)' --output text)
    printf "  %-40s %5s\n" "$BUCKET" "$OBJECT_COUNT"
    TOTAL_BUCKETS=$((TOTAL_BUCKETS + 1))
done

echo ""
echo "Total buckets: $TOTAL_BUCKETS"
echo "========================================"

What does this code do?

  • printf " %-40s %s\n" prints text in aligned columns. %-40s left-aligns a string in a 40-character-wide column. %5s right-aligns in a 5-character column.
  • for BUCKET in $BUCKETS; do ... done loops through each bucket name, running the body once per bucket.
  • OBJECT_COUNT calls aws s3api list-objects-v2 on each bucket with a JMESPath query that counts the objects.
  • TOTAL_BUCKETS=$((TOTAL_BUCKETS + 1)) uses arithmetic expansion to increment the counter after each bucket is processed.
  • Save the file with Ctrl+O, press Enter, then exit with Ctrl+X.
  • Run the report by running:
./s3-report.sh

You should see a formatted table with your my-bash-project-<timestamp> bucket and an object count of 2.

What if I see more buckets?

The report lists every bucket in your AWS account, not just the ones from this project. If you have other buckets from previous work, they will show up too. That is expected.

Not seeing a formatted table?

  • Make sure the new code starts on the line directly after the BUCKETS=... line with no overlap or missing lines.
  • Check that you used backtick-free quotes in the printf lines. The format string must be in double quotes.
  • Why is my s3-report.sh not printing a formatted table?

✔️ Awesome, I've got everything!

Great. Double check you saved s3-report.sh before moving on.

ⓧ I'd like to double check the full code

Here is the complete s3-report.sh so far. Compare it with yours line by line.

#!/bin/bash

echo "========================================"
echo "       S3 Inventory Report"
echo "========================================"
echo "Generated: $(date)"
echo ""

BUCKETS=$(aws s3api list-buckets --query 'Buckets[].Name' --output text)

printf "  %-40s %s\n" "BUCKET NAME" "OBJECTS"
printf "  %-40s %s\n" "----------------------------------------" "-------"

TOTAL_BUCKETS=0

for BUCKET in $BUCKETS; do
    OBJECT_COUNT=$(aws s3api list-objects-v2 --bucket "$BUCKET" --query 'length(Contents)' --output text)
    printf "  %-40s %5s\n" "$BUCKET" "$OBJECT_COUNT"
    TOTAL_BUCKETS=$((TOTAL_BUCKETS + 1))
done

echo ""
echo "Total buckets: $TOTAL_BUCKETS"
echo "========================================"
Create a second empty test bucket

Your report only has one bucket to show right now. A good test needs more data. Adding a second bucket that is completely empty will show how the report handles different states.

  • Create an empty test bucket by running:
aws s3 mb s3://my-bash-project-empty

You should see a make_bucket confirmation message in the terminal.

Seeing a BucketAlreadyExists error?

S3 bucket names must be globally unique across all AWS accounts. If someone else already owns my-bash-project-empty, try a slightly different name like my-bash-project-empty-123. The report will pick it up either way.

Why can't I create my S3 bucket?

Run the report and spot the gap

You now have two buckets. One has files in it, and one is completely empty. Before you run the report, take a second to think.

What do you expect the object count to show for the empty bucket? Will it say 0?

  • Run the report again by running:
./s3-report.sh

The bucket with files shows an object count of 2. But the empty bucket shows None instead of 0. That is not a typo in your script.

Why does the empty bucket show None?

When a bucket has zero objects, the S3 API response does not include a Contents key at all. The JMESPath query length(Contents) tries to measure something that does not exist, which evaluates to null.

The --output text flag then renders that null value as the string None. This is a real gap in the script's reliability. A production report cannot show None where a number should be.

Your report works, but it has a gap. Empty buckets show None instead of 0, and the script would silently continue past errors in production. In the next step, you'll add error handling that makes this script truly reliable.

Add Production-Quality Error Handling

Your report script runs and lists every bucket in your account. You also spotted an issue in the last step: my-bash-project-empty shows "None" instead of "0" for its object count.

That "None" happens because the AWS API does not include a Contents key when a bucket is empty. In this step, you'll fix that query and add a safety header that stops the script immediately if anything goes wrong.

In this step, get ready to:
  • Add a set -euo pipefail safety header and an account info line to your report.
  • Fix the JMESPath query so empty buckets show 0 instead of None.
  • Run the improved report and verify the fixes.
Add the safety header and account info

Production Bash scripts start with a safety header that catches errors immediately instead of letting them snowball. You'll also add a line that prints which AWS account the report is for.

  • Open s3-report.sh in nano by running:
nano s3-report.sh
  • Find #!/bin/bash at the very top of the file.
  • Add this line directly below it:
set -euo pipefail
  • Now scroll down and find the line that says echo "Generated: $(date)".
  • Add this new line directly below it:
echo "Account:   $(aws sts get-caller-identity --query 'Account' --output text)"

What do these additions do?

set -euo pipefail is three safety flags combined into one line.

  • -e exits the script immediately if any command fails.
  • -u treats undefined variables as errors instead of silently expanding to empty strings.
  • -o pipefail makes a pipeline fail if any command in the pipe fails, not just the last one.

The new Account: line runs aws sts get-caller-identity and pulls just the account number. This makes it clear which AWS account the report covers.

  • Save the file by pressing Ctrl+O, then Enter, then Ctrl+X to exit nano.
  • Run the report to see your new header by running:
./s3-report.sh

You should see a new Account: line in the report header displaying your 12-digit AWS account ID. The empty bucket still shows "None" for now.

Not seeing your account ID?

  • Make sure you added the echo "Account:..." line below the echo "Generated:..." line, not above it or on the same line.
  • If you see the script stop with an error, check that set -euo pipefail is on its own line directly under #!/bin/bash with no extra characters.

Help me debug my s3-report.sh safety header and account info line.

Fix the query and add an empty-bucket check

The report still shows "None" for empty buckets. The root cause is in the JMESPath query your script uses to count objects.

  • Open s3-report.sh in nano again by running:
nano s3-report.sh

First, you'll add a guard that handles the case where your account has zero buckets at all.

  • Find the line that starts with BUCKETS=. It looks like this:
BUCKETS=$(aws s3api list-buckets --query 'Buckets[].Name' --output text)
  • Add the following code on new lines directly below it:
if [ -z "$BUCKETS" ]; then
    echo "No buckets found in this account."
    exit 0
fi

What does this check do?

  • [ -z "$BUCKETS" ] tests whether the BUCKETS variable is empty. The -z flag means "zero length".
  • If there are no buckets, the script prints a message and exits with exit 0 (success) instead of trying to loop over nothing.

Without this guard, the for loop would run on an empty list and print only the table header with no rows.

Now for the main fix. The current query asks for length(Contents), but when a bucket is empty, the API response does not include a Contents key at all. JMESPath evaluates length(null) as null, which the CLI renders as "None".

  • Scroll down to the for loop and find this line:
    OBJECT_COUNT=$(aws s3api list-objects-v2 --bucket "$BUCKET" --query 'length(Contents)' --output text)
  • Replace length(Contents) with length(Contents || `[]`), so the line looks like this:
    OBJECT_COUNT=$(aws s3api list-objects-v2 --bucket "$BUCKET" --query 'length(Contents || `[]`)' --output text)

What does the || `[]` fix?

The || is JMESPath's OR operator. It says: "Use Contents if it exists. If it's null, fall back to an empty array `[]`."

length([]) evaluates to 0 instead of null. That gives you a clean number for every bucket.

  • Save the file by pressing Ctrl+O, then Enter, then Ctrl+X to exit nano.
Run the improved report

Before you run the script, think about what you expect to see. Will the empty bucket still show "None", or will the fix produce something different?

  • Run the updated report by running:
./s3-report.sh

The empty bucket now shows 0 instead of "None". The report header includes your AWS account ID, and the bucket with files still shows 2.

Why does this matter in production?

Without set -euo pipefail, a failing AWS command would silently continue and the report could show stale or missing data. With the safety header, the script stops at the first error so you know exactly where things went wrong.

The null-safe query prevents misleading output. A report that says "None" looks broken. A report that says "0" is accurate and trustworthy.

Still seeing None for the empty bucket?

  • Double-check that the query inside the for loop reads length(Contents || `[]`) with backticks around [], not regular quotes or square brackets alone.
  • Make sure the entire query is wrapped in single quotes: 'length(Contents || `[]`)'. Double quotes would cause Bash to interpret the backticks as command substitution.

Help me fix the JMESPath query in my s3-report.sh script.

✔️ Awesome, I've got everything!

Make sure you've saved s3-report.sh and that your report output shows 0 for the empty bucket.

ⓧ I'd like to double check the full code

Scroll up and confirm each of these edits is present in your s3-report.sh, in order:

  • set -euo pipefail on the line directly after #!/bin/bash.
  • The echo "Account:..." line directly after the echo "Generated:..." line.
  • The if [ -z "$BUCKETS" ] block directly after the BUCKETS= line.
  • The query inside the for loop reads length(Contents || `[]`) instead of length(Contents).

Your report script is now production-quality. It catches errors, handles edge cases, and gives accurate output. Next up, you'll build a cleanup script that safely deletes only the buckets your project created.

Build a Safe Cleanup Script

Your report script gives you a clear picture of every bucket in your account. Now you need a way to clean up the project's buckets without accidentally touching anything else.

In this step, you will build a cleanup script that uses functions, arrays, pattern matching, and a confirmation prompt to safely delete only the buckets your project created.

In this step, get ready to:
  • Create a cleanup function that empties and deletes a single S3 bucket.
  • Build a filtering loop that targets only your project's buckets.
  • Run the cleanup script and verify the buckets are gone.
Create the cleanup script with a function

Deleting an S3 bucket requires two steps: first, remove all the objects inside it, then delete the empty bucket. A Bash function lets you bundle those two steps together so you can reuse them for every bucket.

  • Create s3-cleanup.sh by opening it in nano:
nano s3-cleanup.sh
  • Paste the following code into the editor:
#!/bin/bash
set -euo pipefail

# Only target buckets with this prefix
PREFIX="my-bash-project-"

# Function to empty and delete a single bucket
cleanup_bucket() {
    # $1 is the first argument passed to the function
    local bucket_name=$1

    # First remove all objects, then delete the bucket
    echo "  Emptying bucket: $bucket_name"
    aws s3 rm "s3://$bucket_name" --recursive

    echo "  Deleting bucket: $bucket_name"
    aws s3 rb "s3://$bucket_name"

    echo "  Bucket $bucket_name deleted."
}

What does this code do?

  • set -euo pipefail is the same safety header you added to s3-report.sh. It stops the script immediately if any command fails, which is especially important for a deletion script.
  • PREFIX stores the naming pattern your project uses. The script will only delete buckets whose names start with this prefix.
  • cleanup_bucket() is a function that takes a bucket name as its first argument ($1). The local keyword scopes the variable to the function so it does not leak into the rest of the script.
  • aws s3 rm --recursive deletes all objects in the bucket, and aws s3 rb removes the now-empty bucket itself.
  • Save and exit nano by pressing Ctrl+O, then Enter, then Ctrl+X.
  • Make the script executable by running:
chmod +x s3-cleanup.sh
  • Confirm the file exists by running:
ls

You should see s3-cleanup.sh listed alongside s3-manage.sh and s3-report.sh.

Don't see s3-cleanup.sh?

  • Make sure you pressed Ctrl+O and then Enter to save in nano. Pressing Ctrl+X alone exits without saving.
  • Check that you are in the ~/s3-toolkit directory by running pwd.
  • Still stuck? Help me figure out why my file didn't save in nano.
Add the filtering loop and confirmation prompt

The function knows how to delete a bucket, but the script does not know which buckets to target yet. You need a loop that scans every bucket in your account and picks out only the ones matching the my-bash-project- prefix.

  • Reopen s3-cleanup.sh in nano:
nano s3-cleanup.sh
  • Navigate to the bottom of the file (below the closing } of the function) and add the following code:
echo "=== S3 Cleanup Script ==="
echo "Looking for buckets with prefix: $PREFIX"
echo ""

# Get all bucket names from your AWS account
BUCKETS=$(aws s3api list-buckets --query 'Buckets[].Name' --output text)

# Build an array of only the buckets that match the prefix
MATCHING_BUCKETS=()
for BUCKET in $BUCKETS; do
    if [[ "$BUCKET" == ${PREFIX}* ]]; then
        MATCHING_BUCKETS+=("$BUCKET")
    fi
done

# If no buckets match, exit early
if [ ${#MATCHING_BUCKETS[@]} -eq 0 ]; then
    echo "No buckets found matching prefix '$PREFIX'."
    echo "Nothing to clean up."
    exit 0
fi

# Display which buckets will be deleted
echo "Found ${#MATCHING_BUCKETS[@]} bucket(s) to delete:"
for BUCKET in "${MATCHING_BUCKETS[@]}"; do
    echo "  - $BUCKET"
done

What does this code do?

  • MATCHING_BUCKETS=() declares an empty array. As the loop finds matching buckets, it appends each one with +=.
  • [[ "$BUCKET" == ${PREFIX}* ]] uses Bash pattern matching. The * matches any characters after the prefix, so only buckets starting with my-bash-project- are included.
  • ${#MATCHING_BUCKETS[@]} returns the length of the array. If it is zero, the script exits early with a message.
  • Save and exit nano (Ctrl+O, Enter, Ctrl+X).
  • Test the filtering by running the script now:
./s3-cleanup.sh

The script prints the header and lists the two matching buckets (my-bash-project-<timestamp> and my-bash-project-empty), then exits. The deletion code is not in the script yet, so nothing is deleted.

Why test the filtering first?

Running the script before adding the deletion code lets you confirm that the prefix filter works correctly. If you see an unexpected bucket in the list, you can fix the pattern before the script has the power to delete anything.

The filtering works. Now add the confirmation prompt and the loop that actually calls the cleanup function.

  • Reopen the script in nano:
nano s3-cleanup.sh
  • Navigate to the bottom of the file (below the last done) and add the following code:
echo ""
# Ask for confirmation before deleting
read -p "Delete these buckets? (yes/no): " CONFIRM

# Cancel if the user doesn't type "yes"
if [ "$CONFIRM" != "yes" ]; then
    echo "Cleanup cancelled."
    exit 0
fi

# Delete each matching bucket using the function
echo ""
for BUCKET in "${MATCHING_BUCKETS[@]}"; do
    cleanup_bucket "$BUCKET"
done

echo ""
echo "Cleanup complete. ${#MATCHING_BUCKETS[@]} bucket(s) removed."
echo "=== Done ==="

What does this code do?

  • read -p prints a prompt and waits for the user to type a response. The response is stored in the CONFIRM variable.
  • The script only proceeds if the user types exactly yes. Anything else cancels the operation and exits safely.
  • The final loop iterates through the MATCHING_BUCKETS array and calls cleanup_bucket for each one, passing the bucket name as an argument.
  • Save and exit nano (Ctrl+O, Enter, Ctrl+X).

Seeing a syntax error when you save?

  • Make sure you pasted the new code below the existing code, not inside the cleanup_bucket function. The function's closing } must appear before the echo "=== S3 Cleanup Script ===" line.
  • Check that every for loop has a matching done, and every if has a matching fi.
  • Still stuck? Help me fix a syntax error in my s3-cleanup.sh script.
Run the cleanup and verify the buckets are gone

The cleanup script is complete. Before you run it, think about what you expect to happen.

You have two buckets that start with my-bash-project-. Do you think the script will find both of them? What will happen when you type yes at the prompt?

  • Run the cleanup script:
./s3-cleanup.sh

The script prints the header, then lists the two matching buckets and asks for confirmation.

  • Type yes and press Enter to confirm the deletion.

You should see the script empty and delete each bucket one by one, finishing with a "Cleanup complete" message.

What should I see?

The output shows each bucket being emptied (objects removed with aws s3 rm --recursive) and then deleted (with aws s3 rb). The empty bucket has no objects to remove, so the emptying step finishes instantly for that one.

Now verify the buckets are actually gone.

  • Run your report script to confirm by running:
./s3-report.sh

The report should no longer list any my-bash-project- buckets. If you have other buckets in your account, they will still appear untouched. The cleanup script only targeted the ones matching your prefix.

Buckets still showing up in the report?

  • Make sure you typed exactly yes (lowercase, no extra spaces) at the confirmation prompt. Any other input cancels the cleanup.
  • Check the cleanup script output for error messages. If you see a permission error, verify your AWS credentials are still configured by running aws sts get-caller-identity.
  • Need help? Help me figure out why my cleanup script did not delete the S3 buckets.

✔️ Awesome, I've got everything!

Great. Double check you have saved s3-cleanup.sh and that the project buckets are gone from your report.

ⓧ I'd like to double check the full code

Open s3-cleanup.sh in nano and confirm these sections appear in order, top to bottom:

  • Shebang line (#!/bin/bash) and set -euo pipefail safety header.
  • PREFIX variable set to "my-bash-project-".
  • cleanup_bucket() function with local bucket_name=$1, aws s3 rm --recursive, and aws s3 rb.
  • Script header echoes, bucket fetching with aws s3api list-buckets, and the MATCHING_BUCKETS array with the pattern matching loop.
  • Empty-array check, display of matching buckets, read -p confirmation prompt, cancellation guard, and final deletion loop calling cleanup_bucket.

Your S3 Toolkit is complete. You have a management script that creates resources, a report script that inventories them, and a cleanup script that safely removes them. Next up, take on the Secret Mission and add a --dry-run safety mode to your cleanup script.

Secret mission

Add a --dry-run Safety Mode

Your cleanup script works, but what if you want to preview what it would delete before it actually deletes anything? In this mission, you will add a --dry-run flag that simulates the entire cleanup without touching AWS. This is a standard safety pattern used by professional cloud engineers.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. The S3 buckets created in this project store only a few kilobytes of text, so ongoing charges are a fraction of a penny per month.

Cost warning

S3 Standard storage in us-east-1 costs $0.023 per GB per month. The text files in this project total a few kilobytes, making the monthly cost less than $0.01.

DELETE requests to remove your buckets are free. If you already ran s3-cleanup.sh during the project, your buckets are already gone and there is nothing to be charged for.

Resources you used:

  • S3 buckets with the my-bash-project- prefix on AWS.
  • Script files and sample data in ~/s3-toolkit/ on your local machine.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your scripts or extending them with new S3 operations.

  • Any remaining S3 buckets with the my-bash-project- prefix will continue to accrue storage charges of less than $0.01 per month for the text files created in this project.
  • Your scripts in ~/s3-toolkit/ remain on your machine at no cost.
  • AWS CLI v2 stays installed in your WSL environment for future projects.

Pause - I'll come back to this later

Delete your S3 buckets to stop any storage charges. Your local scripts stay on your machine so you can revisit them later.

  • Delete any remaining S3 buckets by running the cleanup script in your WSL terminal:
cd ~/s3-toolkit
./s3-cleanup.sh

If the script reports no matching buckets, they were already deleted during the project. Your scripts in ~/s3-toolkit/ remain available for when you come back.

Delete - I don't want to use this again

Remove all project resources from both AWS and your local machine.

  • Delete any remaining S3 buckets by running the cleanup script in your WSL terminal:
cd ~/s3-toolkit
./s3-cleanup.sh

If the script reports no matching buckets, they were already deleted during the project.

  • Delete the project directory and all local files by running:
cd ~
rm -rf ~/s3-toolkit
  • Confirm the directory is gone by running:
ls ~/s3-toolkit

You should see an error message saying the directory does not exist. That confirms everything has been removed.

Nice Work!

Nice Work!

You just built a complete AWS S3 toolkit using three professional Bash scripts. You can now create cloud resources, generate inventory reports, and safely clean up after yourself, all from the command line.

You've learned how to:

  • Write Bash scripts that automate real AWS CLI operations, using variables, echo statements, and command substitution to create S3 buckets and upload files in a single command.
  • Build an inventory report script using for loops, JMESPath queries, and printf formatting, plus production-quality error handling with set -euo pipefail and null-safe queries.
  • Create a cleanup script with functions, arrays, pattern matching, and a confirmation prompt that safely deletes only the buckets your project created.
  • Secret Mission: Added a --dry-run safety mode that simulates cleanup without making changes, a standard pattern in professional cloud engineering.

Ready to quiz yourself?