transfer.sh 1.6.1 curl 上传下载命令详解
transfer.sh v1.6.1 命令行使用全解:curl 文件/管道/打包上传、Max-Days 与 Max-Downloads 限时限次、服务端与 gpg 端到端加密、GET 下载与 inline 预览、Range 分片、多文件 zip/tar 打包、wget 校验、删除令牌与脚本化嵌入,全部命令与回显实测照抄,含 405/404/429 等边界行为排障。
transfer.sh 1.6.1 curl 上传下载命令详解
目标:把 transfer.sh v1.6.1 的命令行使用一次讲透——curl 上传(文件/管道/打包/过期/加密)、下载(直链/inline/分片/多文件打包/wget)、删除令牌、脚本化嵌入与全部边界行为。transfer.sh 是纯命令行型文件分享服务,curl 就是它的全部"客户端 SDK",本文所有命令与回显均实测照抄。
一、环境信息
- 服务端:transfer.sh v1.6.1,地址
http://192.168.1.172:8132(下文所有 URL 均为此实测服务,替换成自己的服务地址即可;服务端部署见《transfer.sh 1.6.1 Docker Compose 部署指南》,参数调优见《transfer.sh 1.6.1 配置参数详解》) - 客户端系统:Ubuntu 24.04.5 LTS(x86_64)
- 客户端工具:curl 8.5.0(下文版本回显)、wget、gpg(用于客户端加密场景)
curl 版本验证命令与实测回显:
curl --version | head -2
curl 8.5.0 (x86_64-pc-linux-gnu) libcurl/8.5.0 OpenSSL/3.0.13 zlib/1.2.11 brotli/1.1.0 zstd/1.5.5 libidn2/2.3.7 libpsl/0.21.2 (+libidn2/2.3.7) libssh/0.10.6/openssl/zlib nghttp2/1.59.0 librtmp/2.3 OpenLDAP/2.6.10
Release-Date: 2023-12-06, security patched: 8.5.0-2ubuntu10.16
本文示例用的测试文件:
printf 'transfer.sh curl upload test\nline2 for range test' > hello.txt
二、工具概览:协议面与 URL 结构
transfer.sh 不需要专门客户端,整套协议就是三个 HTTP 动词:
| 操作 | HTTP 动词 | URL 形态 | 关键请求头/参数 |
|---|---|---|---|
| 上传 | PUT | http://服务地址/文件名 | curl 用 --upload-file,可选 Max-Days、Max-Downloads、X-Encrypt-Password 头 |
| 下载 | GET | http://服务地址/令牌/文件名(别名 /get/…、/inline/…) | 可选 X-Decrypt-Password 头,支持 Range |
| 删除 | DELETE | http://服务地址/令牌/文件名/删除令牌 | 删除令牌来自上传响应头 X-Url-Delete |
上传成功后响应体只有一行:该文件的下载 URL。URL 中的 令牌 是服务端生成的 10 位随机串(如 xo9KSLWTnj),每个上传不同且不可枚举;文件名 取自上传时 URL 路径里写的名字(不是本地文件名)。
下载响应的头信息里还有两个状态头(实测完整响应见 5.1):X-Remaining-Days 与 X-Remaining-Downloads,反映该链接剩余时效/剩余下载次数,未设置限制时显示 n/a。
三、前置条件
- 有可用的 transfer.sh 服务(自建或公开实例),
curl -s http://服务地址/ | head -3能返回说明页:192.168.1.172:8132: Easy file sharing from the command line === made with <3 by DutchCoders - 客户端有 curl 8.x(
--upload-file与-r分片在各版本 curl 中语义一致); - 准备好测试文件(见第一章
printf命令)。
四、上传命令详解
4.1 基础上传:--upload-file
curl -s --upload-file hello.txt http://192.168.1.172:8132/hello.txt
http://192.168.1.172:8132/xo9KSLWTnj/hello.txt
这一行输出就是分享用的下载 URL。要点:
--upload-file让 curl 以 PUT 方式发送文件,等价写法是-T hello.txt;- URL 路径末段的
hello.txt是保存名,与本地文件名无关——本地叫hello.txt、URL 路径写todelete.txt,下载时就叫todelete.txt(4.1 与第六章的删除实测即基于此); - 上传响应体只有下载 URL 一行;响应头里的删除链接需要加
-D -才能看到,见第六章。
带响应头查看的上传(同一命令加 -D -)实测完整回显:
HTTP/1.1 200 OK
Content-Type: text/plain
Server: Transfer.sh HTTP Server
X-Made-With: <3 by DutchCoders
X-Served-By: Proudly served by DutchCoders
X-Url-Delete: http://192.168.1.172:8132/tAXL0NQUAb/todelete.txt/lNFU5oScQWGdsKe0uqdm
Date: Sun, 01 Nov 2026 04:22:43 GMT
Content-Length: 49
http://192.168.1.172:8132/tAXL0NQUAb/todelete.txt
X-Url-Delete 就是删除链接(含一次性删除令牌),只在响应头里出现,不在响应体里——想留删除能力,上传时就必须带 -D -。
4.2 管道上传:--upload-file -
--upload-file - 让 curl 从标准输入读取内容上传,任意命令的输出都能直接变成分享链接:
cat /etc/os-release | curl -s --upload-file - http://192.168.1.172:8132/os-release.txt
http://192.168.1.172:8132/vKNyDlRSUi/os-release.txt
stdin 没有文件名,所以 URL 路径里写的 os-release.txt 就是保存名,务必显式写好(含扩展名,下载方依赖它)。
4.3 目录打包上传(tar 管道)
目录不能直接上传,先打包再走管道(这也是官方 README 推荐的目录分享方式):
tar -czf - projdir | curl -s --upload-file - "http://192.168.1.172:8132/projdir.tar.gz"
http://192.168.1.172:8132/PIcvKgHpX4/projdir.tar.gz
对方下载后 tar -xzf projdir.tar.gz 还原。zip 同理:zip -r - projdir | curl -s --upload-file - http://…/projdir.zip。
4.4 上传时限制下载次数与有效期
上传时通过请求头指定该链接的寿命,实现"阅后即焚"与"限时有效":
限制下载次数(Max-Downloads)——实测 Max-Downloads: 1:
curl -s -H "Max-Downloads: 1" --upload-file hello.txt http://192.168.1.172:8132/onetime.txt
http://192.168.1.172:8132/r1VO4v159S/onetime.txt
curl -s -w ' [%{http_code}]' http://192.168.1.172:8132/r1VO4v159S/onetime.txt; echo
curl -s -w ' [%{http_code}]' http://192.168.1.172:8132/r1VO4v159S/onetime.txt; echo
transfer.sh curl upload test
line2 for range test [200]
Not Found [404]
第一次下载正常,第二次直接 404——次数用尽链接即失效。
限制有效天数(Max-Days)——实测 Max-Days: 1(一天后过期):
curl -sD - -H "Max-Days: 1" --upload-file hello.txt http://192.168.1.172:8132/maxdays1.txt
HTTP/1.1 200 OK
Content-Type: text/plain
Server: Transfer.sh HTTP Server
X-Made-With: <3 by DutchCoders
X-Served-By: Proudly served by DutchCoders
X-Url-Delete: http://192.168.1.172:8132/CLbsQuqcBz/maxdays1.txt/chu1f5HLxqpTl93Cyz1S
Date: Sun, 01 Nov 2026 07:19:15 GMT
Content-Length: 49
http://192.168.1.172:8132/CLbsQuqcBz/maxdays1.txt
两个边界的实测行为(写脚本时务必注意):
Max-Days传负值会被拒绝,返回 400:Invalid MaxDate, make sure Max-Days is smaller than 290 years HTTP:400Max-Days传超出解析范围的大数(如99999999999999999999)上传仍返回 200,但限制被静默忽略——下载响应头X-Remaining-Days: n/a可证。参数解析失败不报错,脚本里要对取值做前置校验;Max-Downloads传非数字(如abc)同样被静默忽略,实测连下 3 次全部 200(等于没限制)。
过期后的下载行为实测(把某上传的有效期改为过去时间后访问):服务端返回 Not Found、HTTP 404,与"链接不存在"同一表现。
4.5 加密上传
方式一:服务端加密(X-Encrypt-Password)。上传时带密码头,文件以 AES 加密落盘,下载必须带同密码:
curl -s -H "X-Encrypt-Password: q1w2e3r4" --upload-file hello.txt http://192.168.1.172:8132/secret.txt
http://192.168.1.172:8132/r08C4PT8J2/secret.txt
下载时不带密码,拿到的是密文(PGP 报文形态):
-----BEGIN PGP MESSAGE-----
wy4ECQMI5wxylACk9czg1hEkvL5iyqTDM5jwm/BcaufuWvWgD1rJBF9Ne/zvyzYX
0mMBkMFSmjXniB1n0fc8Mm474AnXaz1zHXIJKXP6LsvQ3mBpAPX+TvZsOz1pcTOP
0yhUQY/WR98fyVHVY7kqqZ7DTq62JLzmmXckOyJzPzYHQKThmqqHajqrgF9jQIZp
AO5zUXM=
=j7aU
-----END PGP MESSAGE-----
HTTP:200
带正确密码下载(X-Decrypt-Password 头)得到明文:
curl -s -H "X-Decrypt-Password: q1w2e3r4" http://192.168.1.172:8132/r08C4PT8J2/secret.txt
transfer.sh curl upload test
line2 for range test
HTTP:200
带错误密码下载:
Could not decrypt file
HTTP:500
方式二:客户端加密(gpg 管道)。密码不经过服务器,服务端只存密文,适合更高保密要求:
printf 'transfer.sh curl upload test\nline2 for range test' | \
gpg --batch --yes --passphrase q1w2e3r4 --symmetric --output - | \
curl -s --upload-file - http://192.168.1.172:8132/gpgtest2.txt.gpg
http://192.168.1.172:8132/snBX0zmCC8/gpgtest2.txt.gpg
下载后本地解密还原,与原文逐字节一致:
curl -s http://192.168.1.172:8132/snBX0zmCC8/gpgtest2.txt.gpg | \
gpg --batch --yes --pinentry-mode loopback --passphrase q1w2e3r4 --decrypt --output -
transfer.sh curl upload test
line2 for range test
DIFF: identical
gpg 两个实操细节:解密命令要带 --output -(否则 gpg 默认写文件而非标准输出,管道里表现为"解密没输出");gpg 2.x 批处理场景建议加 --pinentry-mode loopback。
五、下载命令详解
5.1 直接下载:GET
curl -s http://192.168.1.172:8132/fYdpkLW5X8/hello.txt
transfer.sh curl upload test
line2 for range test
下载后与原文比对:
diff hello.txt <(curl -s http://192.168.1.172:8132/fYdpkLW5X8/hello.txt) && echo "DIFF: identical"
DIFF: identical
完整响应头实测(下载类响应的基准形态):
HTTP/1.1 200 OK
Cache-Control: no-store
Connection: keep-alive
Content-Disposition: attachment; filename="hello.txt"
Content-Length: 50
Content-Type:
Server: Transfer.sh HTTP Server
Vary: Range, Referer, X-Decrypt-Password
X-Made-With: <3 by DutchCoders
X-Remaining-Days: n/a
X-Remaining-Downloads: n/a
X-Served-By: Proudly served by DutchCoders
Date: Sun, 01 Nov 2026 06:49:47 GMT
三个细节:Content-Disposition: attachment 表示浏览器访问该 URL 会直接下载(而非展示);X-Remaining-Days/X-Remaining-Downloads 显示该链接剩余时效/次数(未设置时为 n/a,设置了限制后用于确认剩余额度);Vary 里的 X-Decrypt-Password 提示该头会影响响应(加密上传场景)。
5.2 /get/ 与 /inline/ 别名:控制浏览器行为
同一条上传有三种等价访问路径,仅浏览器行为不同(命令行下载三者内容一致):
| 路径 | Content-Disposition | Content-Type | 浏览器行为 |
|---|---|---|---|
/令牌/文件名(裸路径) | attachment | 空值 | 直接下载 |
/get/令牌/文件名 | attachment | 空值 | 直接下载 |
/inline/令牌/文件名 | inline | text/plain; charset=utf-8 | 页面内预览 |
实测三者响应头对比:
-- GET /<token>/hello.txt --
HTTP/1.1 200 OK
Cache-Control: no-store
Connection: keep-alive
Content-Disposition: attachment; filename="hello.txt"
Content-Length: 50
Content-Type:
Server: Transfer.sh HTTP Server
Vary: Range, Referer, X-Decrypt-Password
X-Made-With: <3 by DutchCoders
X-Remaining-Days: n/a
X-Remaining-Downloads: n/a
X-Served-By: Proudly served by DutchCoders
Date: Sun, 01 Nov 2026 06:49:47 GMT
-- GET /get/<token>/hello.txt --
HTTP/1.1 200 OK
Cache-Control: no-store
Connection: keep-alive
Content-Disposition: attachment; filename="hello.txt"
Content-Length: 50
Content-Type:
Server: Transfer.sh HTTP Server
Vary: Range, Referer, X-Decrypt-Password
X-Made-With: <3 by DutchCoders
X-Remaining-Days: n/a
X-Remaining-Downloads: n/a
X-Served-By: Proudly served by DutchCoders
Date: Sun, 01 Nov 2026 06:49:47 GMT
-- GET /inline/<token>/hello.txt --
HTTP/1.1 200 OK
Cache-Control: no-store
Connection: keep-alive
Content-Disposition: inline; filename="hello.txt"
Content-Length: 50
Content-Type: text/plain; charset=utf-8
Server: Transfer.sh HTTP Server
Vary: Range, Referer, X-Decrypt-Password
X-Made-With: <3 by DutchCoders
X-Remaining-Downloads: n/a
X-Served-By: Proudly served by DutchCoders
Date: Sun, 01 Nov 2026 06:49:47 GMT
要分享给浏览器用户直接预览(文本/图片/PDF),把 URL 改成 /inline/ 前缀即可;给脚本/命令行用则用裸路径或 /get/。
5.3 分片下载(Range)
支持 HTTP Range 请求,curl -r 即可分片,适合大文件断点续传:
curl -s -r 0-8 http://192.168.1.172:8132/fYdpkLW5X8/hello.txt
transfer.
curl -s -r 9- http://192.168.1.172:8132/fYdpkLW5X8/hello.txt
sh curl upload test
line2 for range test
-r 0-8 取前 9 字节,-r 9- 取第 10 字节到末尾,两段拼接即完整文件。Content-Range 响应头可确认分片边界,配合 curl -C - 可做简单断点续传。
5.4 多文件打包下载(.zip / .tar.gz)
把多个已上传文件合并成一个归档下载:URL 写成 /(令牌1/文件名1,令牌2/文件名2).zip——外层括号必须带上。实测:
curl -s "http://192.168.1.172:8132/(H31oeloiul/hello.txt,avp90aHDnJ/os-release.txt).zip" -o zipdl.zip -w 'HTTP:%{http_code} size:%{size_download}\n'
HTTP:200 size:738
解包验证:
unzip -l zipdl.zip
Archive: zipdl.zip
Length Date Time Name
--------- ---------- ----- ----
50 2026-11-01 04:41 hello.txt
400 2026-11-01 04:41 os-release.txt
--------- -------
450 2 files
(unzip -l 清单中 Length 为归档前原文件大小,合计 450 字节;size:738 为压缩包实际字节数。)
tar.gz 同理:
curl -s "http://192.168.1.172:8132/(H31oeloiul/hello.txt,avp90aHDnJ/os-release.txt).tar.gz" -o tardl.tar.gz -w 'HTTP:%{http_code} size:%{size_download}\n'
HTTP:200 size:394
tar tzf tardl.tar.gz
hello.txt
os-release.txt
高频坑:不写括号直接访问 /令牌1/文件1,令牌2/文件2.zip 会落入服务器的删除类路由,实测返回:
HTTP:405 size:0
即 405 Method Not Allowed——GET 请求打到了只认 DELETE 的三段路径上。看到 405 先检查括号。
5.5 wget 下载与完整性校验
wget 一样可用:
wget -q http://192.168.1.172:8132/fYdpkLW5X8/hello.txt && cat hello.txt
transfer.sh curl upload test
line2 for range test
md5 双向校验(本地原文件 vs 下载文件):
md5sum hello.txt hello.txt
f269c35d5f79a12ec292846cb6327581 hello.txt
f269c35d5f79a12ec292846cb6327581 hello.txt
(第二条为 wget 重新下载后的校验,哈希一致。)脚本化分发场景建议始终附带 md5/sha256 校验,传输完整性一目了然。
六、删除与令牌
删除链接来自上传响应头的 X-Url-Delete(见 4.1 实测),形态为 下载 URL + /删除令牌。删除操作实测全流程:
curl -sD - --upload-file hello.txt http://192.168.1.172:8132/todelete.txt
(响应头同 4.1,X-Url-Delete: http://192.168.1.172:8132/tAXL0NQUAb/todelete.txt/lNFU5oScQWGdsKe0uqdm)
删除前下载正常(HTTP 200),发起 DELETE:
curl -s -X DELETE http://192.168.1.172:8132/tAXL0NQUAb/todelete.txt/lNFU5oScQWGdsKe0uqdm -w '\nHTTP:%{http_code}\n'
HTTP:200
再下载:
curl -s -w '\nHTTP:%{http_code}\n' http://192.168.1.172:8132/tAXL0NQUAb/todelete.txt
Not Found
HTTP:404
规则要点:
- 删除令牌是一次性随机串,与下载令牌独立;
X-Url-Delete只在上传响应头出现一次,错过就要等文件过期或服务端清理; - 脚本里务必在拿到上传响应时同时抓取
X-Url-Delete(curl -sD -后从头部解析),落库或落盘保存; - 删除请求对 URL 敏感,删除令牌段缺失会得到 404/405 而非删除成功。
七、脚本化嵌入
7.1 封装 transfer() 函数
把"打包 + 上传"封装成 shell 函数(官方 README 模式的实测等价形态),写进 ~/.bashrc 随取随用:
transfer() {
tar -czf - "$1" | curl -s --upload-file - "http://192.168.1.172:8132/$1.tar.gz"
}
实测效果(X13,projdir 为含两个文件的目录):
transfer projdir
http://192.168.1.172:8132/PIcvKgHpX4/projdir.tar.gz
单文件版:
transferf() {
curl -s --upload-file "$1" "http://192.168.1.172:8132/$(basename "$1")"
}
7.2 健壮性写法
- 失败即报错:
-f让 curl 在 HTTP 4xx/5xx 时返回非零退出码,-sf配合||做失败分支:URL=$(curl -sf --upload-file report.pdf http://192.168.1.172:8132/report.pdf) || { echo "upload failed"; exit 1; } - 同时抓下载与删除链接:用
-D -把响应头也导出来解析:RESP=$(curl -sD /tmp/h.txt --upload-file report.pdf http://192.168.1.172:8132/report.pdf) DL_URL="$RESP" DEL_URL=$(grep -i '^X-Url-Delete:' /tmp/h.txt | tr -d '\r' | awk '{print $2}') - 对端校验:上传后立即回读 diff/md5(如 5.1、5.5 所示)再通知对方,避免"传完即坏了"的尴尬;
- 限流配合:服务端若配置了
--rate-limit(行为见《transfer.sh 1.6.1 配置参数详解》5.3),高频脚本会遇到 429,重试逻辑要带上退避等待; - 过期参数前置校验:
Max-Days传负数会 400、传超大数会被静默忽略(4.4 实测),脚本里先做数值范围检查再发请求。
八、注意事项
- URL 括号要转义/引号包裹:多文件打包 URL 含
(、)、,,shell 里必须放双引号内,否则被 shell 解释成子 shell 与变量替换; - stdin 上传必写保存名:管道上传没有文件名,URL 路径的文件名就是对方看到的文件名,别用默认值糊弄;
X-Url-Delete只出现一次:想要删除能力,上传命令必须带-D -(或-D <文件>);- 删除令牌与下载令牌都是机密:拿到两者即可下载与删除,分享下载链接时不要把删除链接一并发出;
- 加密密码不可找回:
X-Encrypt-Password/gpg 密码丢失等于文件作废(服务端存的是密文),密码要随链接一起交给接收方(建议另走安全信道); - 404 的四种可能:令牌写错、文件已被删除(第六章)、
Max-Days已过期(4.4)、Max-Downloads次数用尽(4.4)——按这个顺序排查; - 413 的含义:上传超过服务端
--max-upload-size限制,返回Request Entity Too Large(服务端配置见 C1 5.3); - wget 下载注意输出名:
wget默认用 URL 末段作为本地文件名,URL 末段就是服务端保存的文件名,通常无需-O改名。
九、常见问题
Q1:上传只回了一个 URL,怎么获得删除能力?
上传时加 -D -(或 -D <文件>),从响应头 X-Url-Delete 取删除链接(4.1/第六章实测)。响应体里永远只有下载 URL。
Q2:浏览器打开下载链接是"另存为",想直接预览怎么办?
把 URL 的令牌前加 /inline/(5.2)。文本、图片、PDF 都会走浏览器内置预览。
Q3:多文件打包下载报 405?
URL 少了外层括号。正确形态 /(令牌1/文件1,令牌2/文件2).zip(5.4 实测:不带括号必 405)。
Q4:昨天还好的链接今天 404 了?
按 404 四种可能排查(第八章第 6 条):令牌错 → 被删除 → Max-Days 过期 → Max-Downloads 用尽。可让上传者用服务端数据目录的元数据确认(MaxDate/Downloads 字段,服务端结构见《transfer.sh 1.6.1 配置参数详解》5.2)。
Q5:上传报 413?
超过服务端 --max-upload-size(单位 KB)。联系管理员调整,或压缩/分卷后再传。
Q6:脚本高频调用时报 429?
服务端开了 --rate-limit(每分钟全站请求数上限,429 即触发限流)。给脚本加退避重试,或让管理员调大限流值(C1 5.3)。
Q7:curl 上传中途断了怎么办?
PUT 上传不支持断点续传,重传即可;下载支持 Range/分片(5.3),大文件可用 curl -C - 续传。
Q8:怎么验证下载完整性? 上传前算 md5/sha256,下载后重算比对(5.5 实测形态);tar/zip 归档自带 CRC,解包报错即损坏。
十、参考链接
- 本系列:《transfer.sh 介绍与选型指南》
https://www.quzuo.net/document/nn7i2cwAAhTV、《transfer.sh 1.6.1 Docker Compose 部署指南》https://www.quzuo.net/document/k8d8smHJIjJx、《transfer.sh 1.6.1 配置参数详解》https://www.quzuo.net/document/00FWDHqIggYK - 官方仓库:
https://github.com/dutchcoders/transfer.sh(README 含 usage 示例) - 版本发布页:
https://github.com/dutchcoders/transfer.sh/releases/tag/v1.6.1