Skip to content

Instantly share code, notes, and snippets.

@jessewang-arvatosystems
Last active January 31, 2024 16:44
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save jessewang-arvatosystems/a8cea0f9d165f12c96c2208dd71b3dfd to your computer and use it in GitHub Desktop.
Save jessewang-arvatosystems/a8cea0f9d165f12c96c2208dd71b3dfd to your computer and use it in GitHub Desktop.
AWS S3 Multipart Upload
#!/usr/bin/env bash
# Copyright 2017 Jesse Wang
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# This script requires:
# - AWS CLI to be properly configured (https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html)
# - Account has s3:PutObject access for the target S3 bucket
# Usage:
# bash s3-multipart-upload.sh YOUR_FILE YOUR_BUCKET (OPTIONAL: PROFILE)
# bash s3-multipart-upload.sh files.zip my-files
# bash s3-multipart-upload.sh files.zip my-files second-profile
fileName=$1
bucket=$2
profile=${3-default}
#Set to 90 MBs as default, 100 MBs is the limit for AWS files
mbSplitSize=90
((partSize = $mbSplitSize * 1000000))
# Get main file size
echo "Preparing $fileName for multipart upload"
fileSize=`wc -c $fileName | awk '{print $1}'`
((parts = ($fileSize+$partSize-1) / partSize))
# Get main file hash
mainMd5Hash=`openssl md5 -binary $fileName | base64`
# Make directory to store temporary parts
echo "Splitting $fileName into $parts temporary parts"
mkdir temp-parts
cp $fileName temp-parts/
cd temp-parts
split -b $partSize $fileName
rm $fileName
# Create mutlipart upload
echo "Initiating multipart upload for $fileName"
uploadId=`aws s3api create-multipart-upload --bucket $bucket --key $fileName --metadata md5=$mainMd5Hash --profile $profile | jq -r '.UploadId'`
# Generate fileparts.json file that will be used at the end of the multipart upload
jsonData="{\"Parts\":["
for file in *
do
((index++))
echo "Uploading part $index of $parts..."
hashData=`openssl md5 -binary $file | base64`
eTag=`aws s3api upload-part --bucket $bucket --key $fileName --part-number $index --body $file --upload-id $uploadId --profile $profile | jq -r '.ETag'`
jsonData+="{\"ETag\":$eTag,\"PartNumber\":$index}"
if (( $index == $parts ))
then
jsonData+="]}"
else
jsonData+=","
fi
done
jq -n $jsonData > fileparts.json
# Complete multipart upload, check ETag to verify success
mainEtag=`aws s3api complete-multipart-upload --multipart-upload file://fileparts.json --bucket $bucket --key $fileName --upload-id $uploadId --profile $profile | jq -r '.ETag'`
if [[ $mainEtag != "" ]];
then
echo "Successfully uploaded: $fileName to S3 bucket: $bucket"
else
echo "Something went wrong! $fileName was not uploaded to S3 bucket: $bucket"
fi
# Clean up files
cd ..
rm -R temp-parts
@shishir84
Copy link

Hi, How can we upload the file into sub directories.
e.g.
bucket name is country. There is a folder inside country named province. Then inside province we have city. Now I want to upload the files in city subfolder.
I tried to tweak you code. But no luck.

@jessewang-arvatosystems
Copy link
Author

Hi there, did you try something like bash s3-multipart-upload.sh files.zip 'country/province/city'?

@shadowmodder
Copy link

super helpful! - thanks

@Aymansaif
Copy link

Aymansaif commented Jun 16, 2021

you can achieve it in two steps
1st step to upload as usual
2nd step to move the uploaded file to the destination you specify

add the following to the script :

path to the folder and subfolder using the next format: country/province/city

path=$3

change profile to $4 just as below
profile=${4-default}

at the end of the script add the following command just before # Clean up files
aws s3 mv s3://$bucket/$fileName s3://$bucket/$path/
echo "File $fileName Successfully moved to subfolder $path successfully"

I tried it and worked on my machine
Good Luck!

@k8simulator
Copy link

Hello,
I want to upload a whole folder i.e. /test-folder/*, how can I do that?

Many Thanks for the Script

@jessewang-arvatosystems
Copy link
Author

@k8simulator This can be accomplished by using a for-loop and iterate each file in the folder by calling the script.

@k8simulator
Copy link

@jessewang-arvatosystems Thanks for the quick reply.
so you mean something like this?
for file in * ; do
sleep 1
./s3-multipart-upload.sh
done

but inside the s3-multipart-upload.sh script what should I but as FileName ?

Thanks

@jessewang-arvatosystems
Copy link
Author

@jessewang-arvatosystems Thanks for the quick reply. so you mean something like this? for file in * ; do sleep 1 ./s3-multipart-upload.sh done

but inside the s3-multipart-upload.sh script what should I but as FileName ?

Thanks

Something like what this stackoverflow describes: https://stackoverflow.com/questions/27881739/a-script-that-iterates-over-all-files-in-folder

ex.

find /test-folder -maxdepth 1 -type f -exec ./s3-multipart-upload.sh {} YOUR_S3_BUCKET \;

@k8simulator
Copy link

@jessewang-arvatosystems
thanks for the hint, it helped me further.

But unfortunately, it is still not clear to me, what should be given as fileName in the s3-multipart-upload.sh script?
since the env "fileName" is invoked several times in the script.

@jessewang-arvatosystems
Copy link
Author

The {} part in the sample I wrote is the filename. It is a placeholder that the find command uses.

I think it is easier to understand if you upload one file to see how it works.

Uploads "test.png" to the S3 bucket "my-files"
./s3-multipart-upload.sh test.png my-files

Uploads "pics.zip" to the S3 bucket "my-photos"
./s3-multipart-upload.sh pics.zip my-photos

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment