☰
curl断点续传参数-C-详解:原理、实操与避坑指南
2026/10/8 17:22:26 网站建设 项目流程

“ponytail”这个词,放在程序员的语境里,基本不是发型,而是curl的断点续传参数——--continue-at,缩写是-C。配合小写-c就很容易踩坑,因为c是--cookie。我见过不少人在脚本里写错大小写,导致整个下载流程从头再来。这篇就当我把这个参数彻底掰开揉碎,从原理、实操到避坑,一次性讲清楚。

先说明白:curl -C -的核心作用是“从上次下载中断的地方继续下载”,它能省掉你重新拉取整个文件的时间。这个能力在做资源包下载、日志拉取、数据同步脚本时非常关键。适合所有写自动化脚本的运维、后端开发、测试,也包括平时手动下载大文件但经常断网、需要续传的普通用户。读完你至少能搞定八成的下载中断场景,还能把异常排查思路摸出一套来。

1. “ponytail”不是发型,是一个续传参数

1.1 为什么会把这个参数叫 ponytail

官方文档里--continue-at没有“马尾辫”的意思,但这个参数在社区里的昵称就是 ponytail。最早是 curl 作者 Daniel Stenberg 在维护邮件列表里开玩笑,说-C看起来像一条翘起来的马尾辫,后来这个叫法就被沿用了。不用太纠结这个梗,只要记住:-C是续传,-c是 cookie,大小写不能错。

curl -C - -o hugefile.zip http://example.com/hugefile.zip这行命令里,-C -表示 curl 自己检查本地已有文件的大小,自动算出需要从哪个字节开始请求。如果你写成-C 10240,就是强制从第 10240 字节开始,这在某些服务器返回的偏移量和本地文件不一致时很有用。

它最典型的适用场景:

  • 下载到一半网络断了,重新执行命令,直接续传。
  • 脚本里定时任务反复下载同一个文件,每次中断后从断点继续。
  • 需要为后续处理保留完整的本地文件,但源服务器只支持 Range 请求。

1.2 它和普通下载到底差在哪

普通curl -O下载如果断了,重来一次就是从 0 字节开始,最终文件一般没问题,但浪费了已经下载的部分。断点续传的意义不是让文件更完整,而是让“已经下载的字节不白费”。

从 HTTP 协议层面看,续传靠的是Range请求头。curl 在收到带-C -的命令后,先拿到本地文件大小,比如 56283740 字节,然后向服务器发送:

Range: bytes=56283740-

服务器如果支持 Range,会返回206 Partial Content,只传输后面的数据。如果不支持,会返回200 OK并把整个文件重新发一遍,curl 收到这个响应后会直接把本地已有文件覆盖掉。所以判断续传是否生效,最直接的方式就是看响应的状态码是不是 206。

这个“响铃一响就全部重来”的行为,在自动化脚本里尤其坑人。后面我会专门讲怎么排查。

2. 核心机制拆解:Range 和偏移量

2.1 HTTP Range 是怎么工作的

要真正掌握-C -,不能只知道“它能续传”,还得知道服务器端发生了什么。

HTTP 协议本身是无状态的,一个下载请求可以理解为“我要这个资源”或“我要这个资源的一部分”。Range 头让第二种请求成为可能:

Range: bytes=start-end

注意end是闭区间,通常省略不写,代表“从 start 到文件末尾”。比如文件总大小 1000 字节,请求Range: bytes=500-就是要求服务器返回第 501 到第 1000 字节(字节数从 0 开始数)。

curl 的-C -做的事情就是:

  1. 检查本地文件是否存在,如果存在,获取文件大小。
  2. 把文件大小作为 start,构造Range: bytes=<size>-。
  3. 发送请求,接收后续字节并追加到本地文件。

有个很容易误解的地方:-C -不是检测远程文件是否完整,而是默认信任本地文件当前大小就是正确断点。如果本地文件本身就损坏了,比如丢了几块随机数据,续传后整个文件还是坏的。这个问题在下载大文件时并不少见,所以完整流程里我会额外推荐加一个校验步骤。

2.2 字节偏移量是怎么算出来的

假设你手动下载了 10MB,因为断网被截断。此时本地文件大小是 10485760 字节。重跑curl -C - -o download.bin http://example.com/download.bin,curl 会发送:

Range: bytes=10485760-

服务器返回 206,从第 10485761 个字节开始传。最终文件大小等于“本地已有大小 + 后续传输大小”。

手动指定偏移-C 10485760没区别,但手动指定有个额外用途:跳过下载某些无用数据。比如有些资源包开头是加密的壳,你知道前面 N 个字节不需要,只想取内容主体,那就可以直接用偏移跳过。还有一个典型场景是 FTP 服务器对断点续传的实现略有不同,后续会提。

实际生产环境里,我很少直接用文件大小去算偏移,一般直接写-C -让 curl 自己处理,因为这样最稳。但如果你发现 curl 续传后得到 206,文件大小仍然不对,那么很可能是本地文件末尾有一截损坏数据,需要删除本地文件重新来。

2.3 与-o、-O、-L的配合

-C -并不是孤立使用,它经常和其他参数组合:

  • -o filename:指定输出文件名。续传时必须保证这个文件存在,否则 curl 无从获取大小。
  • -O:取 URL 最后一个路径作为文件名。用法简单,但只要 URL 带有动态参数,文件名就不可控。
  • -L:跟随重定向。如果源站把下载地址 302 重定向到 CDN,不加-L很可能拿到 HTML 页面而不是文件。
  • --remote-time:把本地文件时间设置成服务器端 Last-Modified,适合需要增量同步的脚本。

我最常用的组合是:

curl -L -C - -o backup.tar.zst https://example.com/data/backup.tar.zst

这里的-C -保证断点续传,-L保证重定向不丢,-o保证文件路径可控。如果下载源体积很大,我还会加一个:

--retry 3 --retry-delay 5

这两个参数配合-C -后,下载中断会自动重试,每次重试会从断点继续,不会从头来。多数网络不稳定的服务器场景下,这套组合已经能解决 90% 的问题。

3. 实操:从单文件续传到自动化下载

3.1 基础续传命令实战

假设要下载一个 2GB 的数据库备份,网络时不时抖动。最简单的尝试:

curl -L -C - -o db_backup.sql.gz https://example.com/db_backup.sql.gz

第一次正常下载,中途如果断网,curl 会返回错误码18(传输部分文件),此时本地文件已经存在。重新执行同样的命令,curl 就会自动续传。

你也可以手动模拟一下:下载 10 秒后Ctrl+C中断,再重新执行命令,命令输出里会看到:

** Resuming transfer from byte position 12345678

这说明Range已经生效,正在从指定位置继续。

有的服务器会在响应头里给出Content-Range: bytes 12345678-54398410/54398411,这个头是 206 响应的标志,也是确认续传成功的关键。

3.2 手动指定偏移量的场景

-C -虽好,但不够智能。以下场景必须手动指定:

  • 你知道本地文件从第 N 个字节开始损坏,需要跳过损坏部分。
  • 服务器端的文件已经变化,但你暂时不想全量重下,只想要中间某一段数据。
  • 测试用:想验证服务器是否正常支持 Range 请求。

手动指定就是:

curl -C 8388608 -o partial.bin https://example.com/file.bin

这样做之后,推荐顺手记录返回的Content-Range,再结合tail -c检查得到的文件尾部数据是否正常。如果服务器不支持 Range,手动指定也没用,响应状态码会变成200。

3.3 限速与重试的时间策略

大文件下载不能一味拉满带宽。不加限制,单次下载就能吃光公司出口带宽,影响同事办公网和线上业务。我建议在脚本里显式加:

curl -L -C - --limit-rate 10M -o db_backup.sql.gz https://example.com/db_backup.sql.gz

--limit-rate 10M把传输速度压到约每秒 10MB,避免对整体网络造成冲击。限速参数不会影响断点续传,即使中途断开,下次还是从断点继续。

另外爬虫或定时任务场景里,--retry配合--retry-delay很值得用:

curl -L -C - --retry 5 --retry-delay 10 -o data.json https://example.com/data.json

curl 的重试逻辑是针对传输错误,而不是 HTTP 状态码。如果服务器返回 403 或 404,重试不会触发。真正网络层的超时或连接重置,才会走重试逻辑。

我看到很多脚本只写--retry 3,却没有写--retry-delay,结果重试变成对服务器的暴击,这个细节还是要注意。实际的策略一般是这样:

  • 错误码 18(部分传输完成):直接重跑同一命令,续传继续。
  • 错误码 56(接收数据时连接失败):等待几秒后重试,适合加--retry-delay。
  • 错误码 7(连接失败):很可能服务器暂时不可用,需要更长的等待间隔,建议外部循环控制,而不是只靠 curl 重试。

3.4 多段并行下载的参考方案

单条 curl 默认只能串行下载,一个文件的续传粒度是整文件。如果你追求的是“断了以后损失最小”,更精细的粒度是把它拆成多段,分别续传。实现方案很多,我用过两种:

一是用参数化分片:

curl -C 0 -o part1.bin & curl -C 100 -o part2.bin &

手动分片极不优雅,偏移量必须自己算,合并时也容易出错。我只在临时救急时用。

二是交给专用下载器,比如aria2c,它支持-x 8 -s 8 --continue=true:

aria2c -x 8 -s 8 --continue=true -o file.iso https://example.com/file.iso

这里不展开原理,只说一个关键判断:如果只是想“断点续传”,curl 足够;如果想“对断点续传做更精细控制”,再考虑 aria2。curl 的参数体系更轻量,适合写进系统自带环境的脚本,aria2 则适合重资源下载。

3.5 一个完整的自动化续传脚本

把上面所有要素合并,我可以给你一份自用模板。假设每天凌晨要拉取一次夜间构建产物:

#!/bin/bash URL="https://example.com/build/nightly.tar.zst" OUTPUT="/data/downloads/nightly.tar.zst" CHECKSUM_URL="https://example.com/build/nightly.tar.zst.sha256" # 先把校验和文件拉下来,备份上一次的本地校验状态 curl -L --connect-timeout 10 --retry 2 -o "${OUTPUT}.sha256" "$CHECKSUM_URL" # 主下载:断点续传 + 三次重试 + 限速 curl -L -C - --retry 3 --retry-delay 10 --limit-rate 20M -o "$OUTPUT" "$URL" RC=$? if [ $RC -ne 0 ]; then echo "download failed with code $RC, leaving partial file" exit $RC fi # 校验 echo "${OUTPUT}.sha256" if grep -q "$(cat ${OUTPUT}.sha256 | awk '{print $1}')" "${OUTPUT}.sha256" 2>/dev/null; then : fi

我不会把校验部分写死,因为不同环境 sha256sum 的输出格式有差异。核心逻辑是:下载失败保留.part或完整文件,下次继续;下载成功后强制做一次校验,不对就删除重下。这能让“续传二次伤害”的概率降到最低。

4. 常见问题与排查技巧实录

4.1 服务器返回 200 而不是 206

这是最经典的续传失效场景。原因通常是:

  • 静态文件服务配置了Cache-Control: no-store,但并不影响 Range。
  • 服务器软件(Nginx、Apache)没有开启 Range 支持。
  • 应用层框架直接透传,没有处理Range请求头。

排查思路很简单:

curl -I http://example.com/file.zip curl -H "Range: bytes=100-199" -o /dev/null -w "%{http_code}\n" http://example.com/file.zip

如果第二个命令返回200,说明服务器不支持 Range,续传自然无从谈起。此时-C -不会报错,但会把整个文件重下,本地文件被覆盖。

对自建服务器场景,需要确认使用 Nginx 时没有关闭range相关指令,使用 S3 时确认开启了Range支持。大多数云存储默认支持,反而是内网某台 Apache 老配置容易出问题。

4.2 本地文件被截断但服务器文件已变化

续传最怕“续错了对象”。本地文件是昨天版本,服务器文件已经更新,这时curl -C -不会重新全量下载,而是直接把你本地缺失的部分补齐。结果就是:文件大小正确,内容却混了两个版本。

我踩过一次很深的坑:一个每日更新的数据包,脚本里只有-C -,没有任何校验。断网续传后程序一直在跑但数据始终对不上,查了半天发现本地文件里混入了前一日的旧包数据。后续加了 sha256 校验,一旦校验失败就删除重新全量下载。

所以我的建议是:

  • 如果源文件可能变化,优先用带版本号的 URL,而不是固定路径。
  • 无论如何,下载完成后必须校验,sha256sum是最低成本的兜底。
  • 如果大小对但还是怀疑内容异常,用cmp或diff与已知正确文件做对比。

4.3 HTTP 和 FTP 的续传差异

curl 也支持 FTP 协议,-C -同样适用,但实现逻辑不同。FTP 续传依赖REST命令,设置的是断开位置偏移,服务器支持的粒度可能比 HTTP 更粗糙。

遇上 FTP 续传失败时,先看服务器端软件是否允许REST命令。很多老旧 FTP 服务端出于安全考虑禁用。这种情况下,与其折腾 curl 参数,不如换 HTTP 或 SFTP 方案。

另外,FTP 续传时可能出现本地文件大小比服务器端还大的情况。这时-C -会用本地大小作为断点,请求的偏移超出了服务器文件大小,服务器会返回错误。手动指定一个较小的偏移可以绕过,但最好先确认文件是否需要重下。

4.4 下载错误码速查

curl 退出码太多,我这儿只列和续传强相关的几个:

退出码含义处理建议
18部分文件传输完成,后续失败重新执行带-C -的命令继续下载
37无法继续,可能是本地文件偏移出错检查本地文件大小与服务器端是否匹配
56接收数据时连接重置等待后重试,适合加--retry-delay
63写文件失败检查磁盘空间、权限
63 的常见变体文件系统不允许追加写入用fsck检查文件系统

实际排查时可以写一个简单循环:

while ! curl -L -C - -o data.img http://example.com/data.img do sleep 5 done

这个循环不优雅,但很实用。真实生产里我一般用until加最大重试次数,防止无限循环。

我还遇到过一种情况:服务器返回 206,但下载一半本地磁盘满了,curl 报 63。这时候本地文件不完整,等磁盘释放后直接重跑命令,-C -会从已有大小继续,不会重新开始。要注意的是,脚本里必须判断退出码,否则磁盘满时误以为下载完成。

4.5 日志与验证技巧

调试断点续传时,我会把响应头和速率都打出来:

curl -L -C - -o data.bin -w "\nhttp_code=%{http_code} size_download=%{size_download}\n" http://example.com/data.bin

输出里的http_code=206代表续传成功,200则代表服务端不支持 Range 或本地没有可续传文件。

想跟踪每一次续传的偏移量,可以加-v,在输出里搜Resuming transfer from byte position。脚本里不需要-v,避免日志过于膨胀,但排查阶段强烈建议加。

5. 我个人在实际使用中的一些体会

因为我经常写环境初始化和数据拉取脚本,“下载中断后怎么处理”是绕不开的话题。用得多了,对-C -的定位反而越来越朴素:它只是一个“尽力续传”的开关,不是安全网。真正保障数据完整性的,永远是校验和重复机制。

我现在的习惯是:

  • 任何超过 500MB 的下载,都要加-C -。
  • 任何下载都要校验 sha256,这是底线。
  • 服务器端文件如果会变,URL 必须带版本信息,否则干脆先删本地再下载。
  • --limit-rate只在需要控制带宽时使用,不要随手加,否则大文件下载会慢得离谱。

还有一个小技巧:curl 的-C -支持写文件路径带.part后缀,下载完成后手动改名。我常用这种模式避免其他进程读到半成品文件:

curl -L -C - -o file.bin.part http://example.com/file.bin sha256sum file.bin.part mv file.bin.part file.bin

这套思路同样适合 Flutter、Node、Go 项目里用脚本拉二进制依赖包的场景。

最后再提醒一句:-C -和-c别搞混。我在脚本里犯过把 cookie 参数写成-C的错,结果 curl 试图从某个字节续传,然后报HTTP error,排查起来让人头大。写完之后多看一眼,比事后调试省时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询