AWS CLI

Sharing S3 Photos with a Pre-Signed URL: Building Part 4 of My Bash Scripting Series

Turning a single photo name into a temporary, shareable link with the AWS CLI

In this installment of my shell-scripting-meets-AWS series, I build a small script that generates a temporary shareable link for any photo sitting in my S3 bucket — no console clicking, no manual permission changes, just one command and a link that works on any device.

Picking up where the series left off

This is part four of a script I've been building out step by step, and if you're just joining now, a quick heads-up: this one assumes some things are already in place. Specifically, it assumes you've already got photos landing in an S3 bucket, organized under a photos folder, because earlier scripts in the series handled the upload side of things. If you're following along without that context, the commands here will still make sense, but you'll want to swap in your own bucket and folder structure rather than copying anything verbatim.

The goal for this script was simple to state and mildly annoying to get exactly right: take the name of a photo that's already in my bucket, and hand back a link I can text to someone, drop in a chat, or open on my phone, without making the photo (or the whole bucket) publicly accessible. AWS has a built-in answer for this — pre-signed URLs — and the CLI makes generating one a one-liner. The interesting part, as usual, was in the small decisions around that one line.

Starting the script and setting the bucket variable

I started a new file, 04-sharephoto.sh, and did what I do at the top of basically every script in this series now: set -e. If any command in the script fails, I want the whole thing to stop rather than limp forward and do something unpredictable with a half-finished command. It's a small habit, but it's saved me from chasing confusing downstream errors more than once.

Next came the bucket name. I keep this as a variable at the top of the script rather than hardcoding it into the AWS command itself, mostly so future-me doesn't have to hunt through a long command string to change it later. This is also where I hit a small, honestly kind of funny snag: I went to autocomplete the bucket name like I usually do, and it just wasn't there. I'd expected my shell history or completion to have it cached from earlier scripts, and it didn't. Nothing was actually broken — I just had to type the bucket name out manually instead of leaning on muscle memory. It's a minor thing, but it's the kind of friction that's worth mentioning because it's exactly the sort of moment that trips people up and makes them think something's wrong when it's really just a caching quirk.

One thing worth flagging here, separate from the autocomplete hiccup: I say my bucket name out loud on screen, and if you're building something similar, it's worth pausing on how much of your own infrastructure naming you want visible in anything you record or publish. Bucket names by themselves aren't usually a security hole, but they can hint at account structure or naming conventions, and there's no reason to hand that out for free. If you're scripting along with a tutorial like this, use a placeholder bucket name in anything public-facing, and keep your real one in a local config or environment variable instead.

Simplifying the input to just a photo name

My first instinct was to have the script accept a full path — bucket, folder, filename, the works — as an argument. But sitting with it for a minute, that felt like unnecessary friction for something I'd be running constantly. I already know the photos live under a photos folder inside the bucket. That part of the structure isn't going to change from call to call. The only thing that actually varies each time I run this is which photo I'm sharing.

So I simplified the interface down to just the photo name. The script itself is responsible for knowing where in the bucket that name lives; I just tell it what I want to share. This is a pretty small design choice, but it's the kind of thing that makes a script actually pleasant to use versus technically functional but annoying. If I'd kept the full-path version, every single invocation would've meant re-typing or re-pasting the same bucket-and-folder prefix over and over, which is exactly the kind of repetition a script is supposed to eliminate in the first place.

Building the presign command

With the bucket variable set and the input simplified, the actual core of the script came together fast — it's really just one command doing the heavy lifting: aws s3 presign, pointed at the bucket, the photos subpath, and the photo name passed in as an argument. Most of what I needed here I'd already worked out in an earlier script; the only real adjustment this time was making sure the bucket-plus-photos portion of the path lined up correctly with where files actually get uploaded.

It's worth being upfront that I'm describing the shape of this command rather than treating the exact flags and path syntax as gospel. AWS CLI syntax shifts sometimes, and pre-signed URL generation in particular has a few knobs — expiration time being the big one — that are worth checking against the current AWS CLI documentation before you build this into anything you're relying on. If you're adapting this pattern, don't just trust that a command structure that worked in a tutorial (mine included) is still exactly right by the time you're typing it in. A quick check of aws s3 presign --help, or the official docs, takes thirty seconds and saves you from debugging a typo'd flag later.

The underlying idea, though, holds regardless of exact syntax: a pre-signed URL is a way of taking a private, non-public S3 object and generating a link that grants temporary access to it, without changing the object's actual permissions. The bucket stays locked down. The object stays private. The link itself becomes the thing that grants access, for a limited window of time.

Running the script and watching the link actually work

Before I could run it, I needed to adjust the file's permissions — the usual chmod step to make the script executable, which is easy to forget and then wonder why './04-sharephoto.sh' is throwing a permission error instead of doing anything useful. Once that was sorted, I ran it with ./04-sharephoto.sh and pasted in the name of a photo I already had sitting in the bucket.

What came back was, honestly, a pretty ugly-looking URL — long, full of encoded characters, not something you'd want to type by hand. But that's normal for a pre-signed link; the query string is doing a lot of work encoding the signature and expiration info that makes the whole thing function. I copied it, pasted it into a browser, and the photo loaded. Then, just to make sure this wasn't some local-browser fluke, I opened the same link on my phone. It loaded there too. Pretty cool, honestly — going from "here's a photo name" to "here's a working link that opens the actual photo on a completely different device" in one command is a satisfying kind of payoff, especially after the small friction of the autocomplete moment earlier.

One caveat before you copy this pattern

There's a detail that's easy to gloss over when a demo works this cleanly: a pre-signed URL isn't tied to a specific person. It's tied to the link itself. Anyone who has that URL — not just the friend you meant to send it to — can open it and see the photo for as long as the link stays valid. If you forward it, if it sits in a group chat, if it gets logged somewhere unexpected, it still works for whoever has it.

That's not a flaw in the approach; it's just what "pre-signed" means, and it's worth internalizing before you build this pattern into anything more sensitive than casual photo sharing. For a photo you're fine with a friend seeing, this is a great, low-friction tool. For anything where access genuinely needs to be limited to one specific person, you'd want to layer on something more — authentication, shorter expiration windows, or a different access model entirely. Knowing which category your use case falls into is really the whole ballgame here.

Conclusion

If you've got your own S3 bucket with something in it already, this is a genuinely fun one to try hands-on: swap in your bucket name, point the script at your own folder structure, and generate a link for something you don't mind sharing. Just double-check the current aws s3 presign syntax against the docs before you lean on it for anything real, and keep in mind that whoever holds the link holds the access.

Official AWS references

KEEP PRACTICING THE DECISION

Learn the concept, then test the edge case.

The Solutions Architect Associate course in TutorialRepo combines guided lessons, scenario questions, review, and course-aware explanations.

  All articles