AWS
S3
bash
file existence
cloud storage

AWS S3 How to check if a file exists in a bucket using bash

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If you need to check whether an object exists in Amazon S3 from Bash, use the AWS CLI command that was built for object metadata rather than parsing directory-style output. The most reliable pattern is aws s3api head-object, because it gives you a clear exit status that works naturally in shell scripts.

Use head-object for Exact Object Checks

head-object asks S3 for metadata about one exact key. If the object exists and your credentials are allowed to inspect it, the command exits successfully.

bash
aws s3api head-object \
  --bucket my-bucket \
  --key reports/daily.csv

In a script, the exit code is more useful than the JSON response. That means the cleanest existence check is usually just an if statement:

bash
1if aws s3api head-object \
2  --bucket "my-bucket" \
3  --key "reports/daily.csv" \
4  >/dev/null 2>&1; then
5  echo "object exists"
6else
7  echo "object missing or inaccessible"
8fi

This approach is better than parsing aws s3 ls output because it is explicit, machine-friendly, and less fragile when filenames contain spaces or when the command prints warnings.

Wrap the Logic in a Reusable Function

A shell function keeps deployment scripts readable and makes it easier to handle multiple checks consistently.

bash
1object_exists() {
2  local bucket="$1"
3  local key="$2"
4
5  aws s3api head-object \
6    --bucket "$bucket" \
7    --key "$key" \
8    >/dev/null 2>&1
9}
10
11if object_exists "my-bucket" "releases/app.tar.gz"; then
12  echo "artifact found"
13else
14  echo "artifact missing"
15fi

Because the function returns the CLI exit status, it behaves like any other Bash test command.

Understand What Failure Means

The main trap is assuming every failure means "object does not exist". A failed head-object can also mean:

  • the bucket name is wrong
  • the key is wrong
  • the active profile points to the wrong account
  • the request is sent to the wrong region
  • the caller lacks S3 permissions

When you need to distinguish those cases, capture the error message instead of discarding it.

bash
1error_text=$(aws s3api head-object \
2  --bucket "my-bucket" \
3  --key "reports/daily.csv" \
4  2>&1 >/dev/null)
5status=$?
6
7if [ "$status" -eq 0 ]; then
8  echo "object exists"
9else
10  echo "check failed: $error_text"
11fi

That is especially useful in CI pipelines, where a permission regression can look identical to a missing build artifact if you only read the boolean result.

When a Prefix Search Is Better

If you are not checking one exact key, head-object is the wrong tool. For prefix-style checks such as "does anything exist under this path", use list-objects-v2.

bash
1count=$(aws s3api list-objects-v2 \
2  --bucket "my-bucket" \
3  --prefix "reports/2026-03-" \
4  --query 'KeyCount' \
5  --output text)
6
7if [ "$count" -gt 0 ]; then
8  echo "matching objects exist"
9else
10  echo "no objects under that prefix"
11fi

That solves a different problem. head-object is for an exact key. list-objects-v2 is for discovery under a prefix.

Verify Your AWS Context Early

Many S3 Bash bugs are really credential bugs. Before rewriting the script, confirm which identity and profile are active.

bash
aws sts get-caller-identity
aws configure list

If you use named profiles, include the profile explicitly in the same script that performs the check. If you depend on environment variables such as AWS_PROFILE or AWS_REGION, log them when troubleshooting. Silent context drift is common in local development, CI, and cron jobs.

Why aws s3 ls Is Usually the Wrong Interface

People often write:

bash
aws s3 ls "s3://my-bucket/reports/daily.csv"

This can work interactively, but it is weaker in automation because:

  • the output format is made for humans
  • distinguishing empty output from an error is awkward
  • parsing becomes brittle if you expand the script later

For scripts, prefer the s3api commands because they map more directly to the underlying API and have more predictable return behavior.

Common Pitfalls

  • Parsing aws s3 ls output instead of checking the head-object exit code.
  • Treating all failures as object-not-found instead of also checking identity, region, and permissions.
  • Forgetting to quote bucket and key variables in Bash.
  • Using list-objects-v2 for an exact-object check when head-object is simpler.
  • Debugging shell syntax first when the active AWS profile is actually wrong.

Summary

  • Use aws s3api head-object for exact S3 object existence checks in Bash.
  • Check the exit status rather than parsing CLI output.
  • Capture stderr when you need to tell missing objects apart from access or region problems.
  • Use list-objects-v2 only when you are testing a prefix, not a single key.
  • Verify your AWS identity and profile early when the result looks suspicious.

Course illustration
Course illustration

All Rights Reserved.