Effiziente CLI-Datei-Uploads mit Open-Source-Tools
Wenn ein Upload-Befehl eine URL zurückgibt, sagt das noch nichts darüber aus, ob die heruntergeladene
Datei Ihrer Eingabe entspricht.
Diese Anleitung nutzt curl und einen temporären lokalen Server mit transfer.sh,
um eine nicht leere Datei hochzuladen, herunterzuladen und die Bytes zu vergleichen.
Am Ende haben Sie eine verifizierte Kopie auf dem Datenträger und eine SHA-256-Prüfsumme,
ganz ohne Cloud-Konto.
Beliebte CLI-Upload-Tools vergleichen
Wählen Sie das Tool passend zum empfangenden System. curl liefert den HTTP-Client;
transfer.sh stellt im folgenden Beispiel den Server bereit.
| Tool | Einsatzgebiet | Vor der Auswahl prüfen |
|---|---|---|
s3cmd | Uploads und Synchronisierung mit S3 oder kompatiblem Objektspeicher | Sie benötigen einen Bucket und Zugangsdaten mit Berechtigungen für die vorgesehenen Vorgänge. |
rclone | Kopieren und Synchronisieren zwischen Cloud-Speicher-Backends | Prüfsummen und weitere Funktionen variieren je nach Backend. |
curl | HTTP-Uploads an einen bestehenden Endpunkt | Der Endpunkt bestimmt Methode, Authentifizierung und Antwortformat. |
rsync | Dateien mit einem von Ihnen kontrollierten Rechner synchronisieren | Für die Übertragung über eine Remote-Shell muss rsync auf beiden Rechnern vorhanden sein. |
lftp | FTP- oder SFTP-Übertragungen und Spiegelung | Wählen Sie ein Protokoll und ein Konto, die das Ziel unterstützt. |
Für einen Workflow mit Speicheranbindung lesen Sie Batch-Exporte nach S3 mit signierten PUT-URLs oder rclone mit DigitalOcean Spaces. Diese Aufgaben erfordern eine Konfiguration beim Anbieter. Hier läuft der empfangende Server auf Ihrem eigenen Rechner und wird nach dem Hoch- und Herunterladen entfernt.
transfer.sh zum schnellen Teilen von Dateien nutzen
Dies ist ein anonymer HTTP-Test über Loopback mit einem lokalen Linux-Docker-Daemon. Die URL ist von diesem Rechner aus erreichbar, solange der Container läuft; sie ist kein öffentlicher Freigabelink. Verwenden Sie eine unveränderliche Testdatei ohne sensible Daten. Dieses Beispiel bietet weder dauerhaften Serverspeicher noch Authentifizierung, Wiederaufnahme oder einen Geschwindigkeitsbenchmark.
Die festgelegte Version weist Uploads mit null Bytes zurück. Das folgende Skript lehnt sie ab, bevor es ein Ergebnisverzeichnis oder einen Container erstellt. Wenn leere Dateien zu Ihrer Übertragungsaufgabe gehören, wählen Sie ein Ziel, das sie unterstützt.
Die Befehle wurden unter Linux x86-64 mit Bash 5.3.15, curl 8.22.0, Docker Engine 29.7.2,
GNU coreutils 9.11 und GNU diffutils 3.12 getestet.
Sie benötigen docker, bash, curl,
cmp und sha256sum im Suchpfad für ausführbare Dateien
sowie Zugriff auf den lokalen Docker-Daemon. Ein Remote-Docker-Kontext würde den Server auf einem
anderen Rechner ausführen. Docker dokumentiert, dass
Versionen vor 28.0.0 auf localhost veröffentlichte Ports für Hosts im selben Netzwerksegment zugänglich machen können.
Laden Sie genau das unten verwendete Image herunter:
docker pull dutchcoders/transfer.sh@sha256:9383e66489ab3a7a56bec1b67d2e27d41c072102d515cdc5ab35f913b72e8a09
Dieser Digest legt transfer.sh v1.6.1 fest,
veröffentlicht am 4. Dezember 2023. Betrachten Sie dies als reproduzierbare lokale Übung, nicht als
Empfehlung, dieses Image öffentlich bereitzustellen. Die
Docker-Anleitung des Projekts erläutert, warum ein versioniertes Image
dem veränderlichen Tag latest vorzuziehen ist.
Eine eigene Instanz von transfer.sh ausführen
Speichern Sie das Skript als upload-check.sh in einem Arbeitsverzeichnis für Tests.
Führen Sie es mit Bash aus, statt es per Sourcing in Ihre Shell einzulesen. Es erstellt ein neues
Ergebnisverzeichnis, weist automatisch einen Host-Port zu, wartet auf die Bereitschaft seines eigenen
Containers und entfernt diesen Container, bevor es Erfolg meldet.
Der lokale Speicher und die temporären Upload-Dateien teilen sich einen RAM-basierten Mount mit
64 MiB. Lassen Sie Platz für Metadaten und temporäre Dateien; dies ist eine Übung für kleine Dateien,
kein Dienst für große Dateien.
Der Server erhält einen festen Dateinamen, payload.bin, damit Leerzeichen,
führende Bindestriche und URL-Satzzeichen in Ihrem lokalen Dateinamen nicht zu URL-Syntax werden.
#!/usr/bin/env bash
set -euo pipefail
umask 077
fail() { printf '%s\n' "$*" >&2; exit 1; }
[[ $# -eq 2 ]] || fail 'Usage: bash upload-check.sh INPUT NEW_RESULT_DIRECTORY'
input=$1
result_dir=$2
# Prefix relative paths so a literal "-" is a file, not curl's stdin selector.
[[ $input == /* || $input == ./* || $input == ../* ]] || input=./$input
[[ $result_dir == /* || $result_dir == ./* || $result_dir == ../* ]] || result_dir=./$result_dir
[[ -f $input && -r $input ]] || fail "Input is not a readable regular file: $input"
[[ -s $input ]] || fail "transfer.sh v1.6.1 rejects empty uploads: $input"
port=${UPLOAD_PORT:-0}
[[ $port =~ ^[0-9]{1,5}$ ]] || fail 'UPLOAD_PORT must be 0 or a port from 1 to 65535.'
(( 10#$port <= 65535 )) || fail 'UPLOAD_PORT exceeds 65535.'
port=$((10#$port))
name=${UPLOAD_NAME:-cli-upload-${RANDOM}-${RANDOM}-$$}
image=dutchcoders/transfer.sh@sha256:9383e66489ab3a7a56bec1b67d2e27d41c072102d515cdc5ab35f913b72e8a09
# An existing result directory is never reused or removed.
mkdir -- "$result_dir" || fail "Choose a new result directory: $result_dir"
work=$result_dir/.work
mkdir -- "$work"
verified=0
checksum=''
cleanup() {
status=$?
trap - EXIT
# A CID file identifies only the container this invocation created.
if [[ -s $work/container.id ]]; then
container_id=$(< "$work/container.id")
if ! docker rm --force "$container_id" >/dev/null; then
printf 'Cleanup failed; remove container %s when Docker is available.\n' "$container_id" >&2
status=1
fi
fi
if (( status == 0 && verified == 1 )); then
mv -- "$work/download.part" "$result_dir/download.bin" || status=1
fi
rm -rf -- "$work" || status=1
if (( status == 0 && verified == 1 )); then
printf 'Verified: %s\nSHA-256: %s\n' "$result_dir/download.bin" "$checksum"
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
docker create --cidfile "$work/container.id" --name "$name" --pull=never \
--publish "127.0.0.1:$port:8080" \
--read-only --user 5000:5000 --cap-drop ALL --security-opt no-new-privileges \
--tmpfs /tmp:rw,size=64m,mode=1777 --memory 128m --cpus 1 --pids-limit 64 \
"$image" --provider local --basedir /tmp/data --temp-path /tmp \
> "$work/create.log"
container_id=$(< "$work/container.id")
docker start "$container_id" >/dev/null
binding=$(docker port "$container_id" 8080/tcp)
[[ $binding == 127.0.0.1:* ]] || fail 'Expected a loopback port binding.'
origin=http://$binding
curl_local() {
curl --disable --noproxy '*' --globoff --fail --silent --show-error \
--connect-timeout 2 --max-time 30 "$@"
}
ready=0
for ((attempt=0; attempt<40; attempt++)); do
if [[ $(docker inspect --format '{{.State.Running}}' "$container_id") != true ]]; then
docker logs "$container_id" >&2
fail 'Server exited before readiness.'
fi
if [[ $(curl_local --max-time 1 --output /dev/null --write-out '%{http_code}' \
"$origin/health.html" 2>/dev/null) == 200 ]]; then
ready=1
break
fi
sleep 0.25
done
if (( ready == 0 )); then
docker logs "$container_id" >&2
fail 'Server did not become ready.'
fi
if ! upload_status=$(curl_local --upload-file "$input" --output "$work/url.txt" \
--write-out '%{http_code}' "$origin/payload.bin"); then
docker logs "$container_id" >&2
fail "Upload failed: $input"
fi
[[ $upload_status == 200 ]] || fail "Unexpected upload HTTP status: $upload_status"
url=$(< "$work/url.txt")
path=${url#"$origin/"}
[[ $url == "$origin/"* && $path =~ ^[A-Za-z0-9]+/payload\.bin$ ]] \
|| fail 'Server returned an unexpected download URL.'
if ! download_status=$(curl_local --output "$work/download.part" \
--write-out '%{http_code}' "$url"); then
fail 'Download failed.'
fi
[[ $download_status == 200 ]] || fail "Unexpected download HTTP status: $download_status"
cmp -- "$input" "$work/download.part" || fail "Downloaded bytes differ: $input"
checksum=$(sha256sum < "$work/download.part")
checksum=${checksum%% *}
verified=1
--upload-file veranlasst curl, einen HTTP PUT zu senden.
Mit --fail schlägt der Befehl bei HTTP-Fehlern fehl;
außerdem verlangt das Skript in jeder Phase HTTP 200.
--disable ist die erste Option von curl, damit eine
persönliche Konfigurationsdatei keine Weiterleitungen hinzufügen oder die Anfrage ändern kann.
--globoff verhindert, dass curl eckige und geschweifte Klammern in lokalen
Dateinamen expandiert. Die vom Server zurückgegebene URL muss auf genau den Ursprung verweisen,
den Docker zugewiesen hat.
Abschließend prüft cmp jedes heruntergeladene Byte, bevor das Skript
download.bin behält.
Das Skript räumt beim normalen Beenden, bei Ctrl+C sowie bei TERM- oder HUP-Signalen auf.
Ein Signal, das nur an Bash gesendet wird, kann warten müssen, bis der aktive Befehl zurückkehrt;
jede curl-Übertragung hat eine Frist von 30 Sekunden. Das Skript entfernt Container anhand ihrer bei
der Erstellung vergebenen ID, damit eine Namenskollision keinen fremden Container entfernen kann.
SIGKILL, ein Rechnerabsturz oder der Ausfall des Docker-Daemons können das Aufräumen verhindern.
Mit UPLOAD_NAME können Sie einen wiedererkennbaren Namen für einen Durchlauf wählen;
mit UPLOAD_PORT können Sie einen bestimmten Host-Port anfordern, statt ihn wie
standardmäßig automatisch zuweisen zu lassen. Keine der Optionen macht einen bestehenden Container
oder einen belegten Port wiederverwendbar.
Eine Datei über Ihre Instanz teilen
Erstellen Sie eine kleine binäre Fixture und führen Sie das gespeicherte Skript aus. Die Klammern
begrenzen den Geltungsbereich der Shell-Optionen;
noclobber verhindert das Überschreiben einer bestehenden Datei sample.bin.
Verwenden Sie ein Verzeichnis, in dem sowohl dieser Dateiname als auch upload-result
noch nicht belegt sind.
(
set -euo pipefail
set -o noclobber
printf '\000\377\001\200\012\015\052\000\101\102' > ./sample.bin
bash ./upload-check.sh ./sample.bin ./upload-result
)
Bei Erfolg lautet die Ausgabe für diese zehn Bytes:
Verified: ./upload-result/download.bin
SHA-256: d77823e7a78045d088fa69d1572861efee50fa35240f7555fa860d02d22633d5
Öffnen Sie upload-result/download.bin oder vergleichen Sie die Datei mit sample.bin;
sie bleibt erhalten, nachdem der Server entfernt wurde. Um Ihre eigene Datei hochzuladen, ersetzen
Sie ./sample.bin im Bash-Aufruf und wählen Sie ein neues Ergebnisverzeichnis.
Lassen Sie die Eingabe unverändert, bis der Befehl abgeschlossen ist.
Bei einem Fehler gibt das Skript einen Status ungleich null zurück, entfernt temporäre Downloads
und hinterlässt das neu erstellte Ergebnisverzeichnis ohne verifizierte Kopie. Ein erneuter Durchlauf
mit demselben Ergebnisverzeichnis wird abgelehnt, auch wenn es leer ist.
Ihre CLI-Uploads absichern
Loopback beschränkt dieses Beispiel auf den lokalen Host; andere Prozesse und Benutzer auf diesem Host können den anonymen Endpunkt weiterhin erreichen. Die im Container gespeicherten Dateien verschwinden, wenn er entfernt wird. Die ursprüngliche Eingabe und Ihr verifizierter Download bleiben jedoch auf dem Datenträger. Unverschlüsseltes HTTP dient hier nur dem lokalen Test. Für einen Dienst, der von anderen Rechnern erreichbar ist, wählen Sie authentifiziertes HTTPS und eine Aufbewahrungsrichtlinie, bevor Sie echte Daten senden. Eine nicht erratbare URL authentifiziert nicht die Person, die darüber eine Datei herunterlädt.
Häufige Probleme beheben
| Symptom | Was Sie prüfen sollten |
|---|---|
| Docker kann den Container nicht erstellen oder starten | Stellen Sie sicher, dass das festgelegte Image heruntergeladen wurde und der lokale Daemon erreichbar ist. Ein belegter Port bei UPLOAD_PORT oder ein bereits vorhandener Name bei UPLOAD_NAME lässt den Start fehlschlagen; wählen Sie einen anderen Wert. |
| Der Server beendet sich oder wird nie bereit | Lesen Sie die Container-Diagnose, die vor dem Aufräumen ausgegeben wird. Das Skript lädt erst hoch, wenn sein eigener Server den Healthcheck besteht. |
| Upload oder Download schlägt fehl | Lesen Sie die Fehlermeldung von curl und die Meldung zur jeweiligen Phase. Der temporäre Mount des Servers mit 64 MiB kann voll werden; versuchen Sie es mit einer kleineren Datei. Übertragungen haben ein Zeitlimit von 30 Sekunden. |
| Die heruntergeladenen Bytes weichen ab | Prüfen Sie, ob sich die Eingabe während der Übertragung geändert hat. Eine erfolgreiche HTTP-Anfrage allein reicht nicht aus; das Skript verwirft diesen Download. |
| Das Ergebnisverzeichnis existiert bereits | Wählen Sie ein neues Verzeichnis. Das Skript bewahrt ein früheres Ergebnis, statt es zu ersetzen. |
| Sie benötigen Bandbreitenlimits für eine Cloud-Aufgabe | Verwenden Sie --limit-rate mit s3cmd oder --bwlimit mit rclone; diese Optionen gehören zu unterschiedlichen Tools. |
Wenn Sie diese Prüfung automatisieren, richten Sie sich nach ihrem Exit-Status und geben Sie jedem Durchlauf ein neues Ergebnisverzeichnis. Eine zeitgesteuerte Ausführung macht aus dem temporären Container weder ein dauerhaftes Backup noch einen öffentlichen Dateidienst.
Mit Browser-Uploads fortfahren
Für eine Upload-Oberfläche im Browser bietet Uppy Dateiauswahl und Upload-Fortschritt. Das tus-Protokoll ermöglicht wiederaufnehmbare HTTP-Uploads, sofern der Server es unterstützt. Das sind separate Entscheidungen für Client und Server; dieses PUT-Beispiel mit transfer.sh implementiert tus nicht. Prüfen Sie Ihren CLI-Workflow mit dem lokalen Hoch- und Herunterladen. Wählen Sie anschließend den empfangenden Dienst passend zu den Speicher- und Zugriffsanforderungen Ihrer Anwendung.
