curl断点续传参数-C-详解:原理、实操与避坑指南 “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: bytes56283740-服务器如果支持 Range会返回206 Partial Content只传输后面的数据。如果不支持会返回200 OK并把整个文件重新发一遍curl 收到这个响应后会直接把本地已有文件覆盖掉。所以判断续传是否生效最直接的方式就是看响应的状态码是不是 206。这个“响铃一响就全部重来”的行为在自动化脚本里尤其坑人。后面我会专门讲怎么排查。2. 核心机制拆解Range 和偏移量2.1 HTTP Range 是怎么工作的要真正掌握-C -不能只知道“它能续传”还得知道服务器端发生了什么。HTTP 协议本身是无状态的一个下载请求可以理解为“我要这个资源”或“我要这个资源的一部分”。Range 头让第二种请求成为可能Range: bytesstart-end注意end是闭区间通常省略不写代表“从 start 到文件末尾”。比如文件总大小 1000 字节请求Range: bytes500-就是要求服务器返回第 501 到第 1000 字节字节数从 0 开始数。curl 的-C -做的事情就是检查本地文件是否存在如果存在获取文件大小。把文件大小作为 start构造Range: bytessize-。发送请求接收后续字节并追加到本地文件。有个很容易误解的地方-C -不是检测远程文件是否完整而是默认信任本地文件当前大小就是正确断点。如果本地文件本身就损坏了比如丢了几块随机数据续传后整个文件还是坏的。这个问题在下载大文件时并不少见所以完整流程里我会额外推荐加一个校验步骤。2.2 字节偏移量是怎么算出来的假设你手动下载了 10MB因为断网被截断。此时本地文件大小是 10485760 字节。重跑curl -C - -o download.bin http://example.com/download.bincurl 会发送Range: bytes10485760-服务器返回 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 秒后CtrlC中断再重新执行命令命令输出里会看到** 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.jsoncurl 的重试逻辑是针对传输错误而不是 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 --continuetruearia2c -x 8 -s 8 --continuetrue -o file.iso https://example.com/file.iso这里不展开原理只说一个关键判断如果只是想“断点续传”curl 足够如果想“对断点续传做更精细控制”再考虑 aria2。curl 的参数体系更轻量适合写进系统自带环境的脚本aria2 则适合重资源下载。3.5 一个完整的自动化续传脚本把上面所有要素合并我可以给你一份自用模板。假设每天凌晨要拉取一次夜间构建产物#!/bin/bash URLhttps://example.com/build/nightly.tar.zst OUTPUT/data/downloads/nightly.tar.zst CHECKSUM_URLhttps://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: bytes100-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-delay63写文件失败检查磁盘空间、权限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_code206代表续传成功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排查起来让人头大。写完之后多看一眼比事后调试省时间。