Linux常用命令--curl与wget
摘要
-
curl 与 wget 怎么选,以及两边的常用下载、请求、上传写法
-
重定向、证书校验、失败重试、耗时统计
-
源码安装 curl,以及 HTTPie(
http命令)的基本用法 -
未特别标注的 curl 参数,按 CentOS 8 自带的 curl 7.61.1 来写
curl 和 wget 怎么选
curl 默认把响应打到标准输出,适合看接口、接管道、写脚本。wget 默认按 URL 里的文件名存到磁盘,适合下载文件和递归镜像。
| curl | wget | |
|---|---|---|
| 默认行为 | 响应打到标准输出 | 保存为文件 |
| 整站抓取 | 不做网站镜像 | --mirror / -r 递归下载 |
| 请求构造 | 方法、Header、JSON、上传都直接支持 | 以 GET 下载为主,也能 POST、加 Header |
| 失败与重试 | --retry、-f、-w 适合写进脚本 |
-c 断点续传、-t 重试次数 |
| 常见环境 | Linux、macOS 大多自带 | Linux 大多自带,macOS 通常要另装 |
CentOS Linux 8 已于 2021-12-31 停止维护,默认的 yum 源已经迁入归档。还在用这套系统时,可以临时把系统源换成 https://vault.centos.org 后再执行文中的 yum。
Vault 只是冻结的历史软件包归档,不再提供安全更新,不能视为恢复了系统维护。生产环境应尽快迁移到仍受支持的 Rocky Linux、AlmaLinux、RHEL 或其他系统。EPEL 8 本身仍跟随 RHEL 8 生命周期维护到 2029-05-31,但它依赖的 CentOS Linux 8 基础仓库仍可能因为系统停止维护而安装失败。
curl
版本提示
本文以 CentOS 8 自带的 curl 7.61.1 为基础,不同版本的常见差异如下:
-
7.66.0:增加
--parallel并行传输 -
7.67.0:HTTP 429 开始触发
--retry -
7.73.0:增加
--output-dir,curl --help改为只显示常用参数,完整参数使用curl --help all -
7.76.0:增加
--fail-with-body -
7.82.0:增加
--json -
8.2.0:增加
--ca-native、--proxy-ca-native,可以使用操作系统原生 CA 存储 -
8.3.0:增加命令行变量
--variable和--expand-* -
8.10.0:增加
--skip-existing,并支持用curl --help 参数名查看指定参数的帮助 -
8.13.0:
--url @文件可以从文件读取 URL 列表 -
8.16.0:增加
--follow、--out-null、--parallel-max-host,重试时间支持小数,并把默认最低 TLS 版本提升到 1.2 -
8.19.0:
--limit-rate、--max-filesize支持小数 -
8.20.0:上游构建默认关闭 NTLM,SMB 改成编译时选择启用,并移除 RTMP
-
8.21.0:URL glob 支持命名,并增加 HTTP/3 代理 CONNECT、MASQUE CONNECT-UDP
-
8.22.0:增加实验性的 RFC 9421 HTTP Message Signatures;移除 TLS-SRP,并阻止 SPNEGO 自动回退到 NTLM
截至 2026-09-02,curl 最新稳定版是 8.22.0。先用 curl --version 查看实际版本和编译特性,再决定是否使用较新的参数;发行版自带的 curl 可能会回移安全补丁,但通常不会增加新参数。
查看页面和下载文件
1 | # 查看页面内容,默认 GET,结果打到标准输出 |
--output-dir 从 curl 7.73.0 才有。CentOS 8 的 7.61.1 会报未知参数。
1 | # curl >= 7.73.0,可以指定目录,同时用 -O 保留 URL 中的文件名 |
重定向
-L 会跟随 301/302/303/307/308。其中 301、302、303 会把 POST 改成 GET;307 和 308 保持原来的方法。
-d 本身就会发 POST。不要再加 -X POST,否则每一次重定向都会被强制成 POST。
1 | # 跟随重定向。很多下载链接会先返回 302/301 |
指定方法和请求体
1 | # JSON 要自己设置 Content-Type。-d 和 --data-binary 默认都按表单类型发送 |
curl 7.82.0 起可以用 --json,它会设置 Content-Type: application/json、Accept: application/json 并按 POST 发送。7.61.1 没有这个参数。
1 | curl --json '{"name":"zhangsan"}' url |
上传文件
-F 会自动加上带 boundary 的 Content-Type: multipart/form-data。再手写一个不带 boundary 的 Content-Type,服务端经常解析失败。
1 | # @ 后面是本地路径。type 是文件的 MIME,filename 是服务端看到的文件名 |
认证、Cookie、代理和调试
1 | # HTTP Basic。只写用户名时 curl 会交互式询问密码,避免密码进入 shell 历史 |
更多实用示例
1 | # 把响应头和正文一起输出到标准输出 |
可以通过 curl --version 的 Features 行确认 HTTP/2 等编译特性。CentOS 8 自带的 curl 7.61.1 不支持 --parallel。
curl 8.x 新功能示例
1 | # curl >= 8.2.0:使用操作系统原生 CA 存储 |
curl 8.22.0 的 RFC 9421 HTTP Message Signatures 仍是实验功能,必须在编译时显式启用,官方不建议用于生产环境,也不保证实验阶段的参数保持兼容。可以先用 curl --help all 确认当前构建是否包含 --httpsig-*:
1 | # HTTPSIG_KEY 是十六进制编码的 HMAC 共享密钥 |
FTP
密码写在命令行里时,可能出现在 shell 历史和 ps 输出中。只给用户名,让 curl 交互式询问密码:
1 | # -O 把内容存成 URL 最后一段的文件名 |
在脚本里使用
1 | curl -fsSL url |
这条命令把正文打到标准输出。要落盘时再加上 -o。
-
-s关掉进度和报错,-S在静默时仍把错误打出来。两个一起用,脚本输出里只留下真正的失败信息。 -
-f遇到 404、500 这类 HTTP 错误时,不把错误页正文打出来,并且以非 0 状态退出。脚本可以直接看$?。 -
-L跟随重定向。
-f 并非绝对可靠,认证过程中的 401、407 等状态可能有例外。curl 7.76.0 起,如果既要非 0 退出状态又要保留错误响应正文,可以使用 --fail-with-body。
证书校验失败
curl: (60) SSL certificate problem: certificate has expired 常见有三类原因。
先看本机时间
HTTPS 会检查证书有效期。本机时间快了或慢了,也会报证书过期,或者 certificate is not yet valid。先执行 date 看时间是否正确。
网站证书过期或证书链不完整
站点证书需要在服务器上更新。一时改不了、又确认访问目标没有问题时,可以临时跳过校验:
1 | curl -k https://www.example.com |
-k 只适合临时排查。跳过校验之后无法发现中间人替换证书,传输口令或 Token 的脚本里不要加它。
系统根证书过期
curl -v 输出里的 CAfile: 是这一次实际使用的证书包。
1 | curl -v https://www.example.com |
curl-config --ca 打印的是编译 curl 时写死的默认路径。在 CentOS 8 中,这个命令由 libcurl-devel 软件包提供:
1 | yum install libcurl-devel |
系统自带的证书包优先用软件源更新。CentOS 8 的源如果已经失效,下面这条会失败,需要先换成 vault 源。
1 | yum update -y ca-certificates |
不要直接覆盖 CAfile 指向的系统证书包。CentOS 的证书包由 update-ca-trust 生成,手动修改会在下次更新时丢失,也可能影响其他程序。
源不可用、又必须临时使用新证书包时,可以从 CA Extract 下载 cacert.pem,只对当前命令指定:
1 | curl --cacert /path/to/cacert.pem https://www.example.com |
也可以临时设置 CURL_CA_BUNDLE=/path/to/cacert.pem。如果是给系统增加自建 CA,应把 CA 证书放进 /etc/pki/ca-trust/source/anchors/,再执行 update-ca-trust extract,而不是修改生成后的 bundle。
按状态码判断失败,以及统计耗时
-I 发的是 HEAD。有的服务对 HEAD 和 GET 返回不同状态码,检查页面是否可用时用 GET,也就是不要加 -I。
--retry 只对超时和部分临时错误重试。curl 7.61.1 支持的 HTTP 状态包括 408、500、502、503、504;HTTP 429 从 7.67.0 起才会触发重试。连接被拒绝要另加 --retry-connrefused。
-m 是单次传输的上限。--retry-max-time 会在开始下一次重试前检查累计时间,但不会中断已经开始的那一次请求,因此它不是严格的整体超时;实际总耗时可能超过设置值。需要严格控制脚本总运行时间时,还应由外层的 timeout 或任务调度器限制。
1 |
|
-w 还可以输出这些字段:
-
%{time_namelookup}、%{time_connect}、%{time_appconnect}、%{time_pretransfer}、%{time_starttransfer}、%{time_total}:见下面的计时说明,都是从请求开始算的累计时间 -
%{size_download}、%{size_upload}:下载、上传的字节数 -
%{url_effective}:跟随重定向之后的最终 URL -
%{content_type}:响应的 Content-Type -
%{remote_ip}:连上的服务器 IP
下面这组时间是累计值。time_connect 里已经包含 DNS,time_starttransfer 里已经包含连接、握手和等到首字节的全部时间。
1 | curl -o /dev/null -s -w \ |
1 | DNS 解析完成: 0.002119s |
| 字段 | 含义(从请求开始累计) |
|---|---|
%{time_namelookup} |
DNS 解析完成 |
%{time_connect} |
TCP 连接完成,已包含 DNS |
%{time_appconnect} |
TLS 握手完成,已包含 DNS 和 TCP。纯 HTTP 时为 0 |
%{time_pretransfer} |
即将把请求发给服务器 |
%{time_starttransfer} |
收到响应的第一个字节 |
%{time_total} |
本次传输结束 |
某一段花了多久,用相邻两项相减:
-
DNS =
time_namelookup -
TCP =
time_connect - time_namelookup -
TLS =
time_appconnect - time_connect(纯 HTTP 时time_appconnect为 0,这一项跳过) -
请求发出到首字节 =
time_starttransfer - time_pretransfer。有请求体时,这段还包含上传请求体的时间,不能全部视为服务端处理耗时 -
传输正文 =
time_total - time_starttransfer
按上面的样例,正文传输大约是 2.364 秒,等到首字节几乎可以忽略。
源码安装
有的系统没装 curl:yum install curl -y。需要较新版本时再从源码装。不要复制旧文章里的固定版本号:旧版 curl 可能包含已经公开的安全漏洞。安装前到 curl releases 选择当前版本,并按 curl 签名说明 校验下载文件的签名或摘要。
装到 /usr/local 后,/usr/local/bin/curl 会和系统的 /usr/bin/curl 并存。动态库版本对不上时,会出现 error while loading shared libraries。只是临时试用时,把下面的安装前缀改成 $HOME/.local,避免碰系统自带的 curl。
编译并安装到 /usr/local
1 | # gcc/make 用于编译,openssl-devel 提供 TLS,zlib-devel 提供压缩 |
7.73.0 起,curl --help 只列出常用参数,完整列表是 curl --help all。CentOS 8 自带的 7.61.1 没有分类帮助,curl --help 就是完整列表;在这个版本上执行 curl --help all 会把 all 当成 URL。完整说明用 man curl。
wget
1 | # 下载到当前目录,文件名取 URL 的最后一段。curl 默认打到标准输出,wget 默认存文件 |
更多实用示例
1 | # -O - 把正文输出到标准输出,-q 关闭 wget 自身的日志,适合接管道 |
POST、Header 和 Cookie
wget 多数时候用来做 GET 下载,也可以带请求体和 Header。
1 | # 这部分作为 POST 请求体 |
镜像整个网站
1 | wget --mirror --convert-links --adjust-extension --page-requisites --no-parent http://example.com |
-
--mirror:等同于-r -N -l inf --no-remove-listing。也就是递归下载、按时间戳跳过没有更新的文件、不限制深度,并保留 FTP 目录列表生成的.listing文件。-l inf表示深度没有上限。 -
--convert-links:下载完成后,把页面里的链接改成本地相对路径,方便离线打开。 -
--adjust-extension:服务器返回的是 HTML,但 URL 没有.html时,补上合适的扩展名。 -
--page-requisites:下载页面显示要用到的图片、CSS 等。默认不跨主机,其他域名上的资源要再加-H(--span-hosts)。 -
--no-parent:不访问起始 URL 所在目录的上级目录。
递归会连续请求。wget 默认遵守 robots.txt。加上请求间隔,避免把对方站点打满:
1 | wget --mirror --wait=1 --random-wait --convert-links --adjust-extension \ |
--accept / --reject 只决定哪些文件被保存。-m 仍然会遍历站点:
1 | # 只保存 rar |
还可以限制递归范围和本地目录结构:
1 | # 只跟随 example.com 域名内的链接,并且不访问 /docs/ 的上级目录 |
--accept-regex / --reject-regex 依赖 wget 的正则支持,可以先执行 wget --help 确认当前版本是否提供这些参数。
http 命令是什么(HTTPie 简介)
HTTPie 是一个面向人的命令行 HTTP 客户端,用来调试 API。安装后有两个命令:http 发请求,https 强制走 HTTPS。
和 curl 相比,它的输出带颜色、语法更接近自然语言,并且会自动处理 JSON 的 Content-Type 和格式化。curl 仍然更适合写进脚本,也更常见于服务器上。
1 | # CentOS 上通常先启用 EPEL。EPEL 8 仍跟随 RHEL 8 生命周期维护, |
1 | http GET https://api.github.com |
HTTPie 的会话文件会以明文保存认证信息、Cookie 和自定义 Header。不要在会话中保存敏感信息;确实需要时,应保护好 HTTPie 配置目录及会话文件的读取权限。
| 特性 | HTTPie(http) | curl |
|---|---|---|
| 可读性 | 输出带颜色,默认排版友好 | 更接近协议本身 |
| JSON | 自动设置 Content-Type 并格式化输出 | 自己设置 Header,并用 --data-binary 发送 |
| 请求怎么写 | 接近 方法 URL 字段=值 |
由 -H、-d、--data-binary 这些参数拼出来 |
| 调试 REST API | 交互时少打很多参数 | 同样能做,命令更长 |
| 脚本和服务器 | 需要单独安装,CentOS 上一般走 EPEL | 多数系统已经带有,退出码和 -w 更适合脚本 |