我们的立场如下:我们是 Geonode,主要销售代理服务,因此关于通过代理下载文件,需要坦诚指出的是:带宽成本占了绝大部分,而且文件体积很大。 如果将一个 4 GB 的下载任务通过计费的家庭宽带(0.79 美元/GB)进行传输,单个文件的成本约为 3.16 美元;而通过数据中心网络(0.14 美元/GB)传输,成本仅为 0.56 美元。 如果您的下载无需伪装成来自普通用户连接——而大多数下载确实不需要——那么使用住宅带宽无异于把钱扔进火里。价格来自我们的定价页面,数据更新于2026年9月。更好的选择是:如果可以直接下载,那就直接下载,这样完全免费。
基于上述说明,以下是正确的操作方法。
命名输出文件的三种方法
默认情况下,curl 会将输出写入标准输出,这就是为什么简单地执行 ``curl https://example.com/file.zip`
` 会使终端屏幕被二进制数据填满。有三个选项可以改变这一行为。
**-o filename
** 会将输出写入你指定的文件名:
curl -o archive.zip https://example.com/download?id=1234
。当你知道想要将文件命名为什么时,请使用此选项,尤其是在 URL 中不包含有用的文件名时。
**-O
** 使用远程文件名。 curl 手册 将其描述为:“将输出写入一个与远程文件同名的文件。仅使用远程文件的文件名部分,路径部分将被截断。”
curl -O https://example.com/files/report.pdf
# saves as report.pdf
请注意该句中的注意事项。由于路径会被舍弃,因此位于不同目录下且基名相同的两个文件会相互覆盖。此外,如果 URL 以斜杠结尾或没有文件名部分,-O
将会失败。
**-J
** 则从服务器的 Content-Disposition
头部获取文件名。文档中说明,该方法会指示 -O
“使用服务器指定的 Content-Disposition 文件名,而不是从 URL 中提取”。这对于 URL 作为不透明标识符的下载端点非常有用。
该选项还附带了手册中最严厉的警告,值得在此重述:
警告:Content-Disposition 文件名不会经过验证或清理。它可能包含路径遍历序列(..),请勿将输出保存到不可信的位置,或在未明确提示的情况下启用此选项。
换言之,-OJ
允许远程服务器在您的文件系统上选择路径。对于您控制的源,这没问题。但在从任意 URL 获取数据的脚本中,这便成为了一个漏洞。如果您需要从不可信来源获取服务器提供的文件名,请先获取头部信息,自行对文件名进行安全处理,然后使用 -o
。
重定向:为何几乎总是需要使用 -L
curl 默认不会跟随重定向。文档中对 -L 的说明是:“跟随 HTTP 重定向,并使用最初指定的方法重复请求。”
这是下载时生成的文件体积很小且包含 HTML 代码,而非您所需内容的最常见原因。 下载 URL 会不断重定向——到 CDN、到签名 URL、到镜像站点、到区域性端点。如果不使用 -L,你保存的将是重定向页面。
curl -L -O https://example.com/latest.tar.gz
有两个相关要点。
-o 的顺序很重要。 当跟随包含多个 URL 的重定向时,curl 会按位置将 -o 参数与 URL 进行匹配。 对于单个 URL 而言这不成问题,但会给编写多 URL 命令的用户带来意外。
限制重定向次数。 --max-redirs 会限制 curl 跟进的重定向次数。默认限制较为宽松,而在 cron 任务中,针对无限制的重定向循环绝非你所希望的故障模式。
隐形失败:将错误页面保存为文件
这是本文中最关键的一点。
默认情况下,curl 会将 HTTP 错误响应视为传输成功。404 错误包含正文;curl 会下载该正文;curl 以 0 退出。 此时,你将获得一个名为 installer.dmg
的文件,其中包含一个显示“未找到”的 HTML 页面,而你的脚本却会继续执行,仿佛一切正常。
使用 --fail
可以解决此问题。手册中写道:“对于状态码为 400 或更高且无响应正文输出的 HTTP 响应,返回错误代码 22 并报错。”
curl --fail -L -O https://example.com/installer.dmg
现在,404 状态码会返回退出代码 22 且不生成文件。你的脚本可以进行检查。
两点改进:
**--fail-with-body
** 功能相同,但会保留响应正文,当 API 返回你希望记录的有用的 JSON 错误消息时,这非常有价值。
**--fail
并非完美。** 它无法捕获服务器返回 200 状态码但显示错误页面的情况(部分服务器确实会这样做)。对于任何重要操作,请验证结果——检查文件大小是否合理,检查是否发布了校验和,或检查魔术字节:
curl --fail -L -o pkg.tar.gz "$URL" || exit 1
file pkg.tar.gz | grep -q gzip || { echo "not a gzip archive"; exit 1; }
因此,对于脚本而言,标准的下载命令行应为:
curl --fail --silent --show-error --location -o output.bin "$URL"
-sS
会隐藏进度条但保留错误消息,这正是自动化场景下所需的行为。若用于交互式操作,-#
会显示简单的进度条,而非默认的进度条。
恢复中断的下载
对于大文件和不稳定的网络连接,此功能至关重要。
-C - 的文档说明如下:“从指定的字节偏移量处恢复之前的传输。使用 '-C -' 指示 curl 自动确定从何处以及如何恢复传输。”
curl -C - -L -O https://example.com/large-file.iso
curl 会检查本地文件的大小,并通过 Range 头部仅请求剩余部分。结合重试机制,这使得长时间的下载也能顺利完成:
curl --fail -L -C - --retry 5 --retry-delay 5 -O https://example.com/large-file.iso
有三点需要注意。
服务器必须支持范围请求。 如果不支持,curl 将无法恢复下载,而是会重新开始或失败。请检查响应头部中是否包含 Accept-Ranges: bytes。
恢复损坏的文件只会得到一个更长的损坏文件。 -C - 会信任本地字节数据。如果部分文件在写入过程中被截断,或者源文件发生了变化,结果会悄无声息地出错。当源文件提供校验和时,请通过校验和进行验证。
**源文件发生变更会导致恢复无效。**如果文件在两次尝试之间被替换,您将获得两个版本的混合体,且不会出现错误提示。
并行下载多个文件
自 curl 7.66 起,-Z
会“并行而非顺序地执行传输”,其中 --parallel-max
用于设置“并行执行的传输最大数量”。
curl -Z --parallel-max 8 --fail -L \
-O https://example.com/a.zip \
-O https://example.com/b.zip \
-O https://example.com/c.zip
对于一个包含 URL 的文件:
xargs -a urls.txt curl -Z --parallel-max 8 --fail -L --remote-name-all
--remote-name-all
会将 -O
的行为应用于每个 URL,从而省去了重复指定该标志的步骤。
请审慎选择并行度。超过一定限度后,并行度越高并不一定越好:服务器会实施速率限制,您的连接会饱和,而且一旦超过平台期,您产生的将是错误而非吞吐量。对于配置良好的服务器,8 是一个合理的起始数值;面对小型主机时,4 或更少则更为礼貌,且通常整体速度更快。 此处的行为与我们在 并发性与并行性 中描述的吞吐量曲线一致——先上升,达到平台期,然后下降。
目录、时间戳和速率限制
三个选项,可省去用户通常需要编写的封装脚本。
**--output-dir
** — “指定保存文件的目录。此选项可与 --remote-name 或 --output 选项配合使用。”无需在前后再添加 cd
。
**--create-dirs
** — 配合 -o
使用时,“curl 会创建必要的本地目录结构。在 Unix 系统中,创建的目录权限为 0750。”请注意权限是 0750,而非 0755。如果其他用户或服务需要读取这些目录,这可能会让你大吃一惊。
curl --fail -L --create-dirs -o data/2026/09/report.pdf https://example.com/report.pdf
**--remote-time
** — “将系统的文件修改日期和时间设置为与远程文件的时间戳一致”。这对镜像操作确实很有用,因为它能让后续工具判断文件的最新状态。
**--limit-rate
** — “将传输速度限制为指定速率”,支持 k
、M
和 G
后缀:
curl --limit-rate 2M -L -O https://example.com/large.iso
其使用价值远高于人们的实际采用率。上行链路饱和会导致网络上的其他所有服务无法使用,而在共享或限速的连接中,设置速率上限是一种基本的礼节。这也能降低服务器将你视为滥用者的可能性。
将实用的选项整合如下:
curl --fail --location --continue-at - --retry 5 \
--remote-time --create-dirs \
--output downloads/archive.tar.gz \
"$URL"
通过代理下载
添加 -x
后,上述所有内容仍然适用,但有三点补充。
curl -x http://user:pass@proxy.example.com:9000 \
--fail -L -O https://example.com/file.zip
需要重新考虑超时设置。 --max-time
并不适用于大小不可预测的下载,因为合法的大文件传输会超过任何固定限制。 请改用基于速度的阻塞检测器:
curl -x "$PROXY" --fail -L \
--connect-timeout 10 --speed-limit 1000 --speed-time 30 \
-O https://example.com/large.iso
该检测器会在吞吐量持续低于每秒 1000 字节且持续 30 秒时中止下载,但不会中断那些虽然缓慢但仍在进行、可能持续数小时的下载。我们在 使用 curl 设置超时 中详细介绍了完整的超时选项。
**恢复行为会与地址轮换机制产生交互。**如果您的代理对每个连接轮换出口地址,则恢复后的传输将来自与原始传输不同的地址。有些服务器可以接受这种情况;有些会提供不同的镜像站点;有些则会拒绝该范围请求。对于长时间的下载,请使用保持相同出口地址的会话。
带宽在你的脑海中是双向计费的,但在现实中只计费一次。 凡是经过代理的每个字节,你都需要付费。若结合 --retry
以及大文件下载至 90% 时发生失败的情况,费用计算很快就会变得令人头疼。因此,在通过代理下载时,请务必使用 -C -
,这样重试时会继续下载而非从头开始。
验证实际下载的内容
退出代码为零表示传输已完成。但这并不意味着您接收到了正确的字节数据;对于任何您打算执行、安装或归档的内容,都应弥补这一差距。
检查文件大小是否合理。 这是最经济实惠的检查方法,可检测到数据截断、错误页面和空响应:
SIZE=$(stat -c%s pkg.tar.gz 2>/dev/null || stat -f%z pkg.tar.gz)
[ "$SIZE" -gt 100000 ] || { echo "suspiciously small: $SIZE bytes"; exit 1; }
检查文件类型。 如果一个 HTML 错误页面被保存为 .tar.gz
扩展名,当页面足够大时,常规大小检查无法识别,但 file
可以轻松识别:
file pkg.tar.gz | grep -q 'gzip compressed' || exit 1
检查已发布的校验和。 如果源头提供了校验和,这是唯一一种验证内容而非格式的方法:
curl --fail -sL -O https://example.com/pkg.tar.gz
curl --fail -sL -O https://example.com/pkg.tar.gz.sha256
sha256sum -c pkg.tar.gz.sha256 || exit 1
请注意这一限制,因为它经常被误解:通过同一连接从同一服务器获取校验和,只能防止文件损坏和截断,而无法防范源头被入侵。 如果服务器提供的是损坏的文件,它也会返回相应的错误校验和。使用 GPG 进行签名验证可以解决这个问题,对于任何需要以特权身份运行的程序,多花这一步是值得的。
**如有提供,请与 Content-Length
进行比对。** 当传输在声明的长度之前结束时,curl 会以错误代码 18 退出并返回非零状态码,这能自动捕获一类截断情况——但仅当服务器声明了长度时才有效,而分块响应并不声明长度。
对于通过代理进行的下载,验证措施应多而非少。 额外的跳点意味着传输被截断或(在过滤型中间件的情况下)被篡改的风险增加。上述检查仅需几毫秒,却能彻底消除一类令人困惑的错误报告。
何时不应使用 curl
curl 在获取 URL 方面表现出色。但对于其他一些相关任务,有更合适的工具。
递归下载和镜像。 curl 仅用于获取您指定的 URL,它不具备爬取功能。若要镜像整个目录或网站,wget -r 或专用的镜像工具才是正确选择——关于两者的对比,我们已在 curl 与 wget 对比 中详细探讨过。
通过不稳定链接下载超大文件。 专用下载管理器在分段传输和积极恢复方面比 curl 表现更佳。配合 -C - 和重试机制的 curl 虽能满足需求,但专用工具效果更佳。
Torrent、rsync、S3 及类似场景。 这些协议各自拥有专属客户端,能够原生处理完整性校验、数据去重和权限管理。aws s3 cp 并非仅仅是在 curl 基础上增加额外步骤。
当存在软件包管理器时。 使用 curl | sh 安装软件虽然方便,但会赋予远程服务器在您的机器上执行任意命令的权限,且无需任何验证。 若有包管理器,请优先使用。
重复下载同一资源。 若按计划获取文件以检查更改,可通过 -z 或 If-None-Match 进行条件请求,从而避免重新下载未更改的内容。缓存比快速下载更优。
大家还问
如何使用 curl 下载文件?
curl -O https://example.com/file.zip 会以远程文件名保存,而 -o name 允许你自定义文件名。实际操作中请添加 --fail 和 -L:如果不添加,curl 将不会跟随重定向,并且会将错误页面保存为请求的文件。
为什么 curl 会下载空文件或 HTML 文件?
几乎总是因为您未跟随重定向——请添加 -L ——或者 HTTP 错误被保存为文件,而 --fail 可以防止这种情况。请使用 file downloaded.zip 检查下载内容,或用文本编辑器打开查看;HTML 错误页面一眼就能认出来。
如何使用 curl 恢复中断的下载?
使用 curl -C - -O <url>。curl 会检查本地文件大小,并通过范围请求获取剩余部分。服务器必须支持范围请求(请查看 Accept-Ranges: bytes),并且请注意:恢复损坏的部分文件会生成一个更长的损坏文件,且不会有警告提示。
如何使用 curl 下载多个文件?
使用 -Z 进行并行传输,配合 --parallel-max 限制并发数,并使用 --remote-name-all 确保每个 URL 都采用远程名称行为。若文件中包含文件列表,可通过 xargs 进行管道传输。请适度控制并行度——随着服务器开始限速,吞吐量会先趋于平稳,随后下降。
如何在 curl 中限制下载速度?
--limit-rate 2M 可将传输速率限制为每秒 2 兆字节,该选项支持 k、M 和 G 后缀。在共享连接或访问小型服务器时值得使用,既能确保网络其他部分保持可用,又能避免被视为滥用行为。
curl 中 -O 和 -o 有什么区别?
-O 使用 URL 中的文件名,并忽略路径。-o 写入到你指定的文件名中。当 URL 中没有可用的文件名、需要特定文件名,或者想要避免不同路径下同名文件发生冲突时,请使用 -o。
curl -OJ 安全吗?
对于不可信的来源,并不安全。手册明确警告称,Content-Disposition 生成的文件名“未经过验证或清理”,且“可能包含路径遍历序列”。这意味着远程服务器将决定文件的存储位置。请获取请求头,自行清理文件名,并使用 -o。
如何检查 curl 下载是否成功?
使用 --fail 选项,使 HTTP 错误返回退出代码 22 而不是保存错误页面,然后检查退出代码。对于重要文件,请进一步验证:检查文件大小是否合理,运行 file 确认文件类型,并在源提供校验和时进行比对。
总结
你在大多数示例中看到的简单 curl -O 命令,在正常情况下都能正常工作,但一旦出错,其故障模式却是最糟糕的那种——返回零退出代码,且生成的文件内容与你请求的不符。
两个参数可以解决这个问题。-L 会跟随几乎所有真实下载链接都会使用的重定向,而 --fail 则能阻止 curl 将 HTTP 错误响应当作内容保存下来。对于大文件,请添加 -C -,因为可恢复下载能让你区分是暂时的网络问题还是需要从头开始下载。
对于脚本,curl --fail --silent --show-error --location 这一行值得记住,当文件较大时则需添加 --continue-at - 和 --retry。无论使用何种参数,都应验证结果,而非仅依赖退出代码——检查文件大小、文件类型,如有校验和则进行校验。下载失败通常是“静默”发生的,而非“显式”报错。