Dateien mit curl und AWS CLI gesammelt nach Amazon S3 exportieren
Erzeugen Sie mit einem kleinen Signierer auf Basis des AWS SDK vorsignierte PUT-URLs. Anschließend
lädt curl jede Datei nach Amazon S3 hoch. Das folgende Bash-Skript exportiert
reguläre Dateien auf der obersten Ebene eines Verzeichnisses, einschließlich versteckter und leerer
Dateien. Es meldet einen Fehler, falls eine der ausgewählten Dateien fehlschlägt. AWS CLI prüft die
Zugangsdaten und ermöglicht eine unabhängige Download-Prüfung; aws s3 presign selbst
signiert Downloads, keine Uploads.
Voraussetzungen
Dieses Beispiel setzt Linux mit Bash, GNU stat,
file, curl, AWS CLI v2 und installiertem Corepack
voraus. Verwenden Sie Node.js 24.15 oder neuer aus der Versionsreihe 24.x oder Node.js 26.x. Dies
sind Voraussetzungen für das Beispiel, keine Mindestanforderungen des AWS SDK zur Laufzeit.
Node führt den TypeScript-Signierer ohne Build-Schritt aus. Falls AWS CLI fehlt, folgen Sie der
Installationsanleitung.
Der Signierer wurde mit Node.js 24.15.0, 26.0.0, 26.5.0 und 26.8.1 geprüft. Der vollständige Upload verwendet AWS SDK 3.1148.0, Yarn 4.12.0, AWS CLI 2.37.10 und curl 8.22.0. Die unten festgelegten Abhängigkeitsversionen machen diese Umgebung reproduzierbar; eine bestehende CLI-Installation muss dafür nicht auf eine ältere Version zurückgesetzt werden.
Sie benötigen außerdem einen bestehenden S3-Bucket für allgemeine Zwecke, dessen Region und ein
authentifiziertes AWS-Profil mit Zugriff auf Ihr Zielpräfix. Nutzen Sie den bestehenden
Anmeldeprozess Ihrer Organisation. CLI und SDK berücksichtigen beide AWS_PROFILE;
Sitzungszugangsdaten aus Umgebungsvariablen müssen AWS_SESSION_TOKEN enthalten.
Speichern Sie keine Zugangsdaten in diesen Skripten.
Erstellen Sie ein separates Projekt mit den beiden Abhängigkeiten des Signierers. Dieser Block belässt Ihre Shell im ursprünglichen Verzeichnis und bricht ab, wenn das Projektverzeichnis bereits existiert:
(
set -euo pipefail
for tool in node corepack aws curl file stat; do
command -v "$tool" >/dev/null || { echo "Missing prerequisite: $tool" >&2; exit 1; }
done
mkdir s3-batch-export
printf '%s\n' '{"private":true,"type":"module","packageManager":"yarn@4.12.0"}' \
> s3-batch-export/package.json
printf 'nodeLinker: node-modules\n' > s3-batch-export/.s3-batch-export.yarnrc.yml
touch s3-batch-export/yarn.lock
cd -- s3-batch-export
YARN_RC_FILENAME=.s3-batch-export.yarnrc.yml corepack yarn add --exact \
@aws-sdk/client-s3@3.1148.0 @aws-sdk/s3-request-presigner@3.1148.0
)
Die leere Datei yarn.lock macht dies zu einem separaten Yarn-Projekt. Die
benannte Konfigurationsdatei verhindert, dass die Installation Dateien namens
.yarnrc.yml aus übergeordneten Verzeichnissen übernimmt. Falls die Installation
nach dem Erstellen fehlschlägt, prüfen Sie das neue Verzeichnis und führen Sie darin denselben
Befehl YARN_RC_FILENAME=… corepack yarn add erneut aus. Führen Sie mkdir nicht
erneut aus und löschen Sie kein bestehendes Projekt. Wechseln Sie nach erfolgreicher Installation
für die weiteren Beispiele in das Projektverzeichnis:
cd -- s3-batch-export
Die Komponenten verstehen
Amazon S3 ist ein skalierbarer Objektspeicher. Eine
pre-signed URL gewährt zeitlich begrenzte Berechtigung für
eine bestimmte Art von S3-Anfrage, ohne Ihre AWS-Zugangsdaten gegenüber der ausführenden Person
offenzulegen. Die Signatur umfasst die HTTP-Methode, den Bucket und Schlüssel, den Ablaufzeitpunkt
und alle Header, die Sie signieren möchten. Eine für GET signierte URL kann daher nicht für PUT
wiederverwendet werden.
Es handelt sich nicht um ein Token zur einmaligen Verwendung. Sie können die URL bis zu ihrem Ablauf wiederverwenden, und ein erfolgreicher PUT ersetzt das Objekt unter diesem Schlüssel. Halten Sie die URL aus Logs, Shell-Tracing und gemeinsam einsehbaren Terminalausgaben heraus. AWS beschreibt diese Berechtigungen und das Ersetzen von Objekten in seiner Anleitung für vorsignierte Uploads.
Der AWS-CLI-Befehl presign
autorisiert GET-Anfragen. Seine Ausgabe kann curl -T nicht autorisieren.
Für die Signatur eines PUT benötigen Sie einen Signierer, mit dem Sie den Vorgang angeben können.
Genau das ermöglicht @aws-sdk/s3-request-presigner.
AWS CLI prüft Ihre Identität und ruft die hochgeladenen Bytes ab;
curl führt die Übertragung aus.
Die erforderlichen IAM-Berechtigungen bereitstellen
Die Signatur selbst wird lokal berechnet. Beim Erzeugen der URL werden daher keine Berechtigungen geprüft. Entscheidend sind die eigenen Berechtigungen des Signierers, denn S3 wertet beim Verwenden der vorsignierten URL die Richtlinie der signierenden Identität aus. Bitten Sie die für den Bucket zuständige Administration um eine Berechtigung, die auf Ihr Ziel beschränkt ist, etwa:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::your-bucket-name/exports/*"
}
]
}
Für den Upload ist s3:PutObject erforderlich. Die unabhängige Download-Prüfung
unten benötigt außerdem s3:GetObject für dieses Präfix. Bucket-Richtlinien und
Richtlinien für Verschlüsselungsschlüssel können zusätzliche Anforderungen vorgeben.
Ersetzen Sie your-bucket-name durch Ihren Bucket und exports/
durch das gewählte Präfix.
Vorsignierte PUT-URLs mit dem AWS SDK erzeugen
Speichern Sie dies als presign-put.ts. Das Skript gibt eine URL aus und beendet sich.
So lässt es sich später einfach aus einer Shell-Schleife aufrufen:
// presign-put.ts - print a pre-signed URL that authorizes PUT, and only PUT.
import { parseArgs } from 'node:util'
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const { values } = parseArgs({
options: {
bucket: { type: 'string' },
key: { type: 'string' },
region: { type: 'string' },
'content-type': { type: 'string', default: 'application/octet-stream' },
'expires-in': { type: 'string', default: '3600' },
},
})
const { bucket, key, region } = values
if (!bucket || !key || !region) {
throw new Error('Usage: node presign-put.ts --bucket B --key K --region R [--content-type T]')
}
const expiresIn = Number(values['expires-in'])
// SigV4 caps a pre-signed URL at 7 days. Reject longer values here rather than
// relying on the SDK's generic expiry error; fractional values are rejected too.
if (!Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 604800) {
throw new Error(`--expires-in must be between 1 and 604800 seconds, got: ${values['expires-in']}`)
}
const client = new S3Client({
region,
maxAttempts: 1,
// curl supplies the body later; omit optional checksum metadata here.
requestChecksumCalculation: 'WHEN_REQUIRED',
})
const url = await getSignedUrl(
client,
new PutObjectCommand({ Bucket: bucket, Key: key, ContentType: values['content-type'] }),
{
expiresIn,
// Require the uploader to send the Content-Type used when signing.
signableHeaders: new Set(['content-type']),
},
)
console.log(url)
Führen Sie es aus:
node presign-put.ts \
--bucket your-bucket-name \
--key exports/localfile.txt \
--region your-region \
--content-type text/plain \
--expires-in 3600
Ersetzen Sie Bucket, Schlüssel und Region durch Ihre Werte. Die URL enthält die Signatur und ihren Ablaufzeitpunkt im Query-String. Sie enthält keinen geheimen Schlüssel, aber jeder, der sie besitzt, kann bis zu ihrem Ablauf wiederholt in dieses eine Objekt schreiben. Halten Sie sie daher aus Logs heraus.
--expires-in ist eine Obergrenze, keine Garantie. Wenn Sie mit temporären
Zugangsdaten aus einer IAM-Rolle, einer SSO-Sitzung oder sts:AssumeRole signieren,
funktioniert die URL nicht mehr, sobald diese Zugangsdaten ablaufen. Das ist oft deutlich früher
als der von Ihnen angeforderte Ablaufzeitpunkt.
X-Amz-SignedHeaders in der erzeugten URL hat den Wert content-type;host.
Dies ist die Liste, die curl exakt reproduzieren muss. Ein abweichender
Content-Type oder das Weglassen des Headers macht die Signatur ungültig.
Die maximale Gültigkeitsdauer bei SigV4 beträgt 7 Tage (604.800 Sekunden).
Dateien mit curl hochladen
Verwenden Sie bei curl die Option -T, um die
lokale Datei für den Upload anzugeben. Mit --retry wird der Upload robuster
gegen vorübergehende Netzwerkprobleme. Der Header Content-Type ist hier nicht
optional: Er gehört zu den signierten Headern und muss daher mit dem Wert von
--content-type übereinstimmen, den Sie dem Signierer übergeben haben. Die Subshell
beschränkt die strikte Fehlerbehandlung auf dieses Beispiel, auch wenn Sie es in eine bestehende
Bash-Sitzung einfügen.
(
set -euo pipefail
CONTENT_TYPE=$(file -b --mime-type -- ./localfile.txt)
# Sign for that exact type, then send that exact type
URL=$(node presign-put.ts \
--bucket your-bucket-name \
--key exports/localfile.txt \
--region your-region \
--content-type "$CONTENT_TYPE")
if status=$(curl --disable -fsS --globoff --retry 3 --retry-delay 2 \
--connect-timeout 10 --max-time 300 --retry-max-time 300 \
-T ./localfile.txt \
-H "Content-Type: $CONTENT_TYPE" \
-o /dev/null -w '%{http_code}' \
"$URL") && [[ "$status" =~ ^2[0-9][0-9]$ ]]; then
echo 'Upload completed.'
else
echo 'Upload failed: expected an HTTP 2xx response.' >&2
exit 1
fi
)
Ersetzen Sie ./localfile.txt durch Ihren Dateipfad sowie Bucket und Region durch Ihre
eigenen Werte. --disable ignoriert die Datei .curlrc des
Aufrufers; --globoff behandelt geschweifte und eckige Klammern in Dateinamen
als literale Zeichen. -f lässt curl bei einem
HTTP-Fehler mit einem Exit-Status ungleich null enden, statt den XML-Fehlertext von S3 wie eine
Erfolgsmeldung auszugeben. -sS unterdrückt die Fortschrittsanzeige,
zeigt aber weiterhin tatsächliche Fehler. Die explizite 2xx-Prüfung weist auch Weiterleitungen
zurück, die -f allein nicht als Fehler behandelt. Fügen Sie
-L nicht hinzu: Das Folgen einer Weiterleitung verändert die signierte
Anfrage. Jeder Versuch hat ein Zeitlimit von 300 Sekunden einschließlich Datenübertragung. Eine
langsame Übertragung einer gültigen Datei kann daher fehlschlagen. Auch das Planen weiterer Versuche
endet nach 300 Sekunden; ein bereits gestarteter Wiederholungsversuch kann weitere 300 Sekunden
laufen. Prüfen Sie nach einer unklaren Antwort das Ziel, bevor Sie den Versuch wiederholen.
Das curl-Handbuch erläutert diese Optionen für Übertragungen,
Wiederholungsversuche und Dateinamen.
Dateiexporte im Batch automatisieren
Speichern Sie dies als batch-export.sh neben presign-put.ts. Das Skript
verarbeitet jeweils eine Datei, fährt nach einem einzelnen Fehler fort und endet mit einem
Exit-Status ungleich null, falls eine ausgewählte Datei fehlschlägt. Es überspringt Verzeichnisse
und symbolische Links, akzeptiert UTF-8-Dateinamen und behandelt ein leeres Verzeichnis als
erfolgreichen Batch mit null Dateien. Verändern Sie das Eingabeverzeichnis während des Laufs nicht.
#!/usr/bin/env bash
set -euo pipefail # Exit on error, undefined variable, or pipe failure
export LC_ALL=C AWS_MAX_ATTEMPTS=1 AWS_PAGER=""
trap 'echo "Batch interrupted; inspect uploaded objects before retrying." >&2; exit 130' INT
trap 'echo "Batch interrupted; inspect uploaded objects before retrying." >&2; exit 143' TERM
BUCKET="your-bucket-name"
EXPIRE=3600 # URL validity in seconds (1 hour)
FILES_DIR="/path/to/your/files" # Directory containing files to upload
REGION="your-region" # Your S3 bucket region
PRESIGN="./presign-put.ts" # The signer from the previous section
PREFIX="${S3_PREFIX:-exports/}" # Use a separate prefix for this batch
TRANSFER_SECONDS="${S3_TRANSFER_SECONDS:-300}"
# --- pre-flight checks ---
if [[ ! -d "$FILES_DIR" || ! -r "$FILES_DIR" || ! -x "$FILES_DIR" ]]; then
echo "Error: Directory '$FILES_DIR' must exist and be readable/searchable." >&2
exit 1
fi
if [[ ! -f "$PRESIGN" ]]; then
echo "Error: Signer '$PRESIGN' not found. See the previous section." >&2
exit 1
fi
for tool in aws node curl file stat; do
command -v "$tool" >/dev/null || { echo "Missing prerequisite: $tool" >&2; exit 1; }
done
if [[ ! "$TRANSFER_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: S3_TRANSFER_SECONDS must be a positive integer." >&2
exit 1
fi
# Verify the credentials the signer will pick up are present and valid.
if ! caller_arn=$(aws sts get-caller-identity --query Arn --output text 2>/dev/null); then
echo "Error: AWS credentials are not configured properly or are invalid." >&2
echo "Check your selected AWS profile or session credentials." >&2
exit 1
fi
echo "AWS credentials verified for: $caller_arn"
echo "Starting batch export from '$FILES_DIR' to bucket '$BUCKET' in region '$REGION'..."
# --- processing loop ---
failed=0
uploaded=0
# dotglob: a hidden file is an ordinary object to S3, and silently leaving
# .env.example behind is data loss. nullglob: an empty directory must not run
# the loop once with a literal '*'.
shopt -s dotglob nullglob
for file in "$FILES_DIR"/*; do
filename=${file##*/}
# Test for a symlink before -f, which follows them: a link inside FILES_DIR
# would otherwise upload bytes from outside it, under a name from inside it.
if [[ -L "$file" ]]; then
echo " Skipping '$filename': symbolic link."
continue
fi
# Skip directories and anything else that is not a regular file.
[[ -f "$file" ]] || continue
echo "Processing '$filename'..."
# Node's argv decoding is lossy for malformed UTF-8; reject before signing a key.
if ! printf '%s' "$filename" | node --input-type=module -e \
'import { readFileSync } from "node:fs"; new TextDecoder("utf-8", {fatal:true}).decode(readFileSync(0))' \
2>/dev/null; then
echo " Error: Filename is not valid UTF-8." >&2
failed=$((failed + 1))
continue
fi
if [[ ! -r "$file" ]] || ! file_size=$(stat -c%s -- "$file"); then
echo " Error: Cannot read or inspect '$filename'." >&2
failed=$((failed + 1))
continue
fi
# This example limits each single PUT to 5 GiB; use multipart for larger files.
if ((file_size > 5368709120)); then
echo " Skipping '$filename': $((file_size / 1024 / 1024)) MiB exceeds this script’s 5 GiB limit." >&2
echo " Use 'aws s3 cp' instead, which switches to multipart automatically." >&2
failed=$((failed + 1))
continue
fi
if ! content_type=$(file -b --mime-type -- "$file"); then
echo " Error: Cannot determine Content-Type for '$filename'." >&2
failed=$((failed + 1))
continue
fi
echo " Signing a PUT for '$filename' (Content-Type: $content_type)..."
if ! url=$(node "$PRESIGN" \
--bucket "$BUCKET" \
--key "$PREFIX$filename" \
--region "$REGION" \
--content-type "$content_type" \
--expires-in "$EXPIRE"); then
echo " Error: Failed to sign a URL for '$filename'." >&2
failed=$((failed + 1))
continue
fi
echo " Uploading '$filename'..."
# The Content-Type must match the signature. curl -f does not reject redirects;
# require a completed 2xx upload without replaying a signed body at another URL.
if status=$(curl --disable -fsS --globoff --retry 3 --retry-delay 2 \
--connect-timeout 10 --max-time "$TRANSFER_SECONDS" --retry-max-time "$TRANSFER_SECONDS" \
-T "$file" \
-H "Content-Type: $content_type" \
-o /dev/null -w '%{http_code}' \
"$url") && [[ "$status" =~ ^2[0-9][0-9]$ ]]; then
echo " Successfully uploaded '$filename'."
uploaded=$((uploaded + 1))
else
echo " Error: Failed to upload '$filename'." >&2
failed=$((failed + 1))
fi
done
echo "Batch export completed: $uploaded uploaded, $failed failed."
# Surface partial failure to the caller: 'set -e' cannot do this for us, because
# every failure above was deliberately caught so the loop could continue.
if ((failed > 0)); then
exit 1
fi
Ersetzen Sie Bucket, Eingabeverzeichnis und Region im Skript und führen Sie es dann aus diesem Projekt heraus aus:
bash batch-export.sh
Bei zwei erfolgreichen Dateien lautet die Zusammenfassung Batch export completed: 2 uploaded, 0 failed. Eine
fehlgeschlagene Übertragung erhöht die Fehleranzahl, auch wenn spätere Dateien erfolgreich sind.
Ein fehlendes oder nicht lesbares Eingabeverzeichnis führt vor jeder Übertragung zum Fehler.
S3_PREFIX überschreibt exports/; geben Sie den
abschließenden Schrägstrich mit an. Namen behalten Leerzeichen, Klammern, Prozentzeichen, Emoji und
Zeilenumbrüche. Namen mit ungültigem UTF-8 werden als Fehler gezählt, weil sie sich über die
String-Argumente von Node nicht zuverlässig abbilden lassen.
Dieses Skript begrenzt Dateien auf 5 GiB und verwendet dieselben Zeitlimits von 300 Sekunden für
Übertragungen und Wiederholungsversuche wie der einzelne Upload. Setzen Sie
S3_TRANSFER_SECONDS für langsamere Übertragungen höher. Pro Datei gibt es höchstens vier
Versuche einschließlich der ersten Anfrage. Ein Wiederholungsversuch sendet die gesamte Datei
erneut; er setzt einen unterbrochenen PUT nicht fort. Bei einer Unterbrechung endet das Skript mit
einem Exit-Status ungleich null und ohne Abschlussübersicht. Fertiggestellte Objekte bleiben
bestehen. Eine verlorene Antwort kann außerdem ein bereits gespeichertes Objekt zurücklassen.
Prüfen Sie daher das Präfix vor einem Neustart.
Uploads ersetzen bestehende Objekte mit demselben Schlüssel. Verwenden Sie ein neues Präfix, um Batches voneinander zu trennen. Verschachtelte Verzeichnisse werden übersprungen, statt ihre Dateien auf einer Ebene zusammenzuführen. Ein erneuter Batch ersetzt die erfolgreich übertragenen Objekte unter ihren Schlüsseln und löscht keine anderen Schlüssel.
Überprüfen Sie eine bekannte Datei unabhängig, indem Sie sie herunterladen und die Bytes vergleichen. Dies erstellt ein neues lokales Verzeichnis und lässt bestehende Dateien unverändert:
(
set -euo pipefail
command -v cmp >/dev/null || { echo 'Missing prerequisite: cmp' >&2; exit 1; }
mkdir s3-download-check || exit $?
aws s3api get-object --bucket your-bucket-name --key exports/localfile.txt \
--region your-region s3-download-check/localfile.txt >/dev/null || exit $?
cmp -- ./localfile.txt s3-download-check/localfile.txt || exit $?
echo 'Downloaded bytes match.'
)
Vergleichen Sie bei der Prüfung eines vollständigen Batches jede erwartete Datei. Eine reine Erfolgsanzahl kann weder eine ausgelassene Datei noch ein falsches Objekt erkennen. Falls der Download fehlschlägt, prüfen und entfernen Sie nur das unvollständige Download-Verzeichnis dieser Prüfung, bevor Sie den Block erneut ausführen.
Bewährte Verfahren für Sicherheit anwenden
Beschränken Sie den Zugriff der signierenden Identität auf das Batch-Präfix und wählen Sie eine
URL-Gültigkeitsdauer, die für die Übertragung ausreicht. Temporäre Zugangsdaten können früher als
--expires-in ablaufen, wie AWS in seiner
Anleitung für vorsignierte URLs erläutert.
Die Skripte ändern weder Bucket-Richtlinien noch Verschlüsselungseinstellungen. Wenn der Bucket
einen vom Kunden verwalteten KMS-Schlüssel verwendet, vergewissern Sie sich vor dem Upload, dass die
Administration den erforderlichen Schlüsselzugriff gewährt hat.
Das Signieren von Content-Type bindet diesen Header, nicht den Dateiinhalt oder
die Sicherheit der Datei. Jeder, der die URL besitzt, kann mit demselben Header andere Bytes senden.
Die unabhängige Download-Prüfung schützt Ihre Überprüfung davor, versehentlich das falsche Objekt
zu vergleichen; sie macht die URL nicht zu einer Richtlinie zur Inhaltsvalidierung.
Häufige Probleme beheben
- Prüfung der Zugangsdaten schlägt fehl: Erneuern Sie Ihre bestehende Anmeldung und prüfen Sie
AWS_PROFILEoder die Umgebungsvariablen der Sitzung. Ein erfolgreicher Aufruf vonaws sts get-caller-identityidentifiziert den Aufrufer der CLI; er beweist nicht, dass S3 einen Schreibzugriff erlaubt. Der Signierer ermittelt außerdem seine eigenen Zugangsdaten. - Upload liefert HTTP 403: Prüfen Sie Bucket-Region, Schlüssel, URL-Ablaufzeitpunkt,
Signierberechtigungen und den exakten signierten
Content-Type. Der Upload-Block verwirft den Antwortkörper von S3 bewusst. Seine allgemeine Fehlermeldung kann daher nicht erkennen lassen, welche dieser Ursachen zutrifft. Protokollieren Sie die signierte URL während der Diagnose nicht. Die AWS-Anleitung zur Upload-Fehlerbehebung erläutert diese Prüfungen. - Zeitlimit der Übertragung überschritten: Das Zeitlimit gilt auch für gültige Dateien unter der Größenbegrenzung. Prüfen Sie das Objekt, passen Sie gegebenenfalls das Zeitlimit an und führen Sie den Upload mit einer neuen URL erneut aus. Zusätzliche Wiederholungsversuche beheben keine Authentifizierungsfehler.
- Datei wird als zu groß übersprungen: Dieses Skript erzwingt eine Obergrenze von 5 GiB für einen einzelnen PUT. Verwenden Sie für eine größere Datei einen Multipart-Client.
Für größere Dateien übernimmt aws s3 cp
den Multipart-Upload. Dies ist eine separate Übertragung mit Zugangsdaten, keine vorsignierte
Anfrage mit curl:
aws s3 cp ./large_file.zip s3://your-bucket-name/exports/large_file.zip --region your-region
Fazit
Verwenden Sie für Ihren ersten Dateiexport ein neues Präfix, laden Sie bekannte Objekte herunter, um ihre Bytes zu prüfen, und kontrollieren Sie den Exit-Status des Skripts, bevor Sie den Batch als abgeschlossen betrachten. Dieser Open-Source-Workflow trennt Signatur und Übertragung und macht zugleich einzelne Fehler sichtbar.
Wenn Ihre Dateien bereits mit Transloadit verarbeitet werden, kann der Robot 🤖 /s3/store Ergebnisse aus einem Step innerhalb derselben Assembly nach S3 exportieren.
