
不用 curl 也能发 HTTP 请求:Bash 内置 /dev/TCP 网络编程指南
Bash 内置了 TCP/UDP 网络能力,通过 /dev/tcp 这个虚拟设备就能直接发起 HTTP 请求、测试端口、搭建简易服务——不需要 curl、wget 或任何额外工具。本文手把手教你掌握这个被低估的 Shell 技能。
原创。你可能不知道,Bash 内置了一套完整的 TCP/UDP 网络能力——通过
/dev/tcp这个虚拟设备,不用 curl、不用 wget、不用任何外部工具,就能发 HTTP 请求、测端口、甚至搭建迷你服务。读完本文,你会在没有 curl 的环境中多一条救命手艺。
你遇到过这种情况吗?
新装的 Docker 镜像、精简版 Alpine 容器、或者某台连 apt 都没配的裸机 Linux——上去第一件事想 curl 一下某个接口,结果发现 curl 没装。装 curl 吧,还得先配包管理源,鸡生蛋蛋生鸡。
这时候要是知道 Bash 自己就能发网络请求,能省多少事?
实际上,Bash 在编译时如果启用了 --enable-net-redirections 选项(绝大多数发行版默认开启),就支持通过 /dev/tcp/HOST/PORT 和 /dev/udp/HOST/PORT 这两个虚拟路径来进行网络通信。这不是真正的设备文件——它们是 Bash 内置的 网络重定向机制,在语法层面上假装成文件路径,背后用的是 socket 系统调用。
—— 广告 ——
基本原理
Bash 把网络连接抽象成文件重定向。当你写:
exec 3<>/dev/tcp/example.com/80Bash 在背后做了三件事:
- 调用
socket()创建一个 TCP socket - 调用
getaddrinfo()解析example.com的 IP 地址 - 调用
connect()建立 TCP 连接
然后把 socket 文件描述符(这里是 3)当作普通文件一样读写。你往 fd 3 写数据,就是发送网络包;从 fd 3 读数据,就是接收响应。
这就是 Bash 网络功能的全部秘密——没有魔法,只有抽象。
实战一:发送 HTTP GET 请求
最简单的例子,从 Bash 直接发起 HTTP 请求:
#!/bin/bash
# 打开 TCP 连接
exec 3<>/dev/tcp/httpbin.org/80
# 发送 HTTP 请求
echo -e "GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n" >&3
# 读取响应
cat <&3
# 关闭连接
exec 3>&-运行这段脚本,你会看到 httpbin.org 返回的完整 HTTP 响应,包括状态行、响应头和响应体。
关键点解释:
exec 3<>/dev/tcp/httpbin.org/80:打开 fd 3,双向连接到 httpbin.org 的 80 端口echo -e ... >&3:把 HTTP 请求写入 fd 3(发送到网络)cat <&3:从 fd 3 读取服务器的响应exec 3>&-:关闭 fd 3
注意 HTTP 协议细节:请求行末尾必须是 \r\n(回车换行),Host 头不可省略(HTTP/1.1 强制要求),Connection: close 告诉服务器响应完后关闭连接——这样 cat 才能读到 EOF 后退出。
实战二:HTTPS 请求
/dev/tcp 本身不支持 TLS 加密。想要发 HTTPS 请求,有两个方案:
方案一:通过 openssl s_client 中转(如果系统有 openssl)
echo -e "GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n" | \
openssl s_client -connect httpbin.org:443 -quiet 2>/dev/null方案二:本地代理转发(8080 端口跑 HTTPS → HTTP 转换器)
这不是日常做法,但如果你在一个完全隔离的环境里,可以写一个 Python 脚本来做 HTTPS → HTTP 桥接。不过说实话——真到了需要 HTTPS 的场景,直接装 curl 更省事。/dev/tcp 的强项是 HTTP(明文)场景和连接测试,HTTPS 是它的短板,没必要硬撑。
实战三:简易端口扫描器
不用 nmap,纯 Bash 写一个端口扫描器:
#!/bin/bash
target=$1
start_port=${2:-1}
end_port=${3:-1024}
echo "扫描 $target 从端口 $start_port 到 $end_port ..."
for ((port=$start_port; port<=$end_port; port++)); do
# 用超时控制——最多等 1 秒
timeout 1 bash -c "echo >/dev/tcp/$target/$port" 2>/dev/null && \
echo " ✅ 端口 $port 开放"
done
echo "扫描完成"用法:./port-scanner.sh example.com 80 100 会扫描 80-100 端口。
原理很简单:尝试向目标 IP 的某个端口建立 TCP 连接。连接成功(echo >/dev/tcp/... 返回 0)说明端口开放;连接失败(超时或被拒)说明端口关闭。
注意线程安全:这个扫描器是串行的,逐个端口测试,扫描 1000 个端口要十几分钟。真要干活还是用 nmap。但它的优势是——任何 Linux 机器上都能跑,零依赖。
实战四:测试远程服务是否存活
写监控脚本时最常用的场景——检查某个 Web 服务是否在回应:
#!/bin/bash
health_check() {
local host=$1
local port=$2
local timeout=${3:-3}
if timeout $timeout bash -c "exec 3<>/dev/tcp/$host/$port" 2>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✅ $host:$port 可达"
return 0
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ❌ $host:$port 不可达"
return 1
fi
}
# 用法
health_check "google.com" 80
health_check "localhost" 3306这个脚本可以直接放进 cron,每 5 分钟跑一次检查服务状态。比用 nc(netcat)更便携——因为 Bash 一定在,nc 不一定。
实战五:发送 HTTP POST 请求
不只是 GET,POST 也一样可以:
#!/bin/bash
exec 3<>/dev/tcp/httpbin.org/80
# 准备请求体
data='{"name":"Bash","type":"shell"}'
content_length=${#data}
# 发送 POST 请求
echo -e "POST /post HTTP/1.1\r\nHost: httpbin.org\r\nContent-Type: application/json\r\nContent-Length: $content_length\r\nConnection: close\r\n\r\n$data" >&3
# 读取响应
cat <&3
exec 3>&-关键点:Content-Length 必须与实际请求体字节数一致,否则服务器不知道什么时候读完。用 ${#data} 计算字符串长度——对于纯 ASCII 的 JSON 是准确的,但如果有中文等多字节字符,需要用 echo -n "$data" | wc -c。
实战六:用 Bash 搭建迷你 HTTP 服务器
这可能有点意外——/dev/tcp 不仅能发起连接,还能接受连接,只要用 -(监听模式)和 /dev/tcp 配合。
不过 Bash 没有原生的 socket 监听语法。要实现监听,得曲线救国:用 nc -l 或 socat 等工具。纯 Bash 的 /dev/tcp 是客户端能力——它不能 listen(),只能 connect()。
如果你想用纯 Bash 实现服务器端,需要借助 coproc 配合 nc 或 socat,但这已经偏离了"不依赖外部工具"的初衷。
实用技巧
超时控制
默认情况下,/dev/tcp 的连接会一直阻塞直到成功或超时(系统默认超时通常很长)。始终用 timeout 包装一下:
timeout 3 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null错误处理
连接失败时,Bash 会把错误信息写到 stderr。捕获它:
if result=$(timeout 3 bash -c "echo >/dev/tcp/$host/$port" 2>&1); then
echo "✅ 成功"
else
echo "❌ 失败: $result"
fiHTTP/1.0 模式
如果不需要持久连接,可以发 HTTP/1.0 请求(不需要 Host 头),但现代服务器(如 Nginx、Cloudflare)对 HTTP/1.0 的支持不完全一致。建议始终用 HTTP/1.1 + Connection: close。
函数封装
把常用操作封装成函数,随时调用:
# GET 请求
http_get() {
local url=$1
local host=$(echo "$url" | sed 's|https\?://||' | cut -d/ -f1)
local path="/$(echo "$url" | cut -d/ -f4-)"
exec 3<>/dev/tcp/$host/80
echo -e "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n" >&3
cat <&3
exec 3>&-
}
http_get "http://example.com/api/status"限制与注意事项
只能 TCP,不支持 UDP 重定向的可靠性:虽然语法上支持 /dev/udp/HOST/PORT,但 UDP 是无连接的,/dev/udp 实际做的是 connect() 到 UDP socket,然后可以 send() / recv()。这不等于 UDP 服务器监听——对于 DNS 查询等简单请求够用,但不能做可靠的双向通信。
没有 DNS-over-HTTPS:域名解析用的是系统 getaddrinfo(),走的是 /etc/resolv.conf 配置的 DNS 服务器,不会走 HTTPS 加密 DNS。
性能问题:每打开一次 /dev/tcp/HOST/PORT,Bash 都会新建一个独立的 TCP 连接。频繁短连接场景下(比如扫描 10000 个端口),开销很大。
不适用于生产环境:Bash 的网络重定向是单线程的,没有连接池、没有 keep-alive、没有 TLS。当调试工具或应急手段很合适,但别拿它写生产级网络服务。
不是什么环境都有:某些嵌入式系统或最小化容器没开 --enable-net-redirections,这时 /dev/tcp 会报 No such file or directory。怎么确认?跑一行 ls -l /dev/tcp——如果报错说不存在,那就是 Bash 没编译这个特性;如果显示目录内容(哪怕/dev/tcp列出来是乱码),说明可用。
和其他工具对比
| 工具 | 依赖 | HTTPS | 便捷性 | 适用场景 |
|---|---|---|---|---|
| Bash /dev/tcp | 无(Bash 内置) | ❌ | 🌟🌟🌟 | 应急、极简环境、快速测试 |
| curl | 需安装 | ✅ | 🌟🌟🌟🌟🌟 | 日常 HTTP/S 请求 |
| wget | 需安装 | ✅ | 🌟🌟🌟🌟 | 下载文件 |
| nc (netcat) | 需安装 | ❌ | 🌟🌟🌟 | 端口测试、数据传输 |
| nmap | 需安装 | ✅ (脚本) | 🌟🌟 | 专业端口扫描 |
总结
/dev/tcp 是 Bash 里最被低估的功能之一。它不是什么高大上的技术——背后就是最朴素的 POSIX socket API,但 Bash 用"文件重定向"这个你早就熟悉的语法把它包装了起来,让你在一个 SSH 窗口里就能完成网络操作。
它的价值不在"替代 curl"(curl 好用多了),而在于它是一种兜底能力。当你在一个连包管理都没有的极简环境中、在 Docker 容器里、在嵌入式设备上——只要有一个 Bash shell,你就有网络通信能力。
下次遇到没有 curl 的机器,别急着 apt install——试试:
echo >/dev/tcp/google.com/80 && echo "网络通了"如果返回成功提示,你的"Bash 网络工具箱"已经就绪了。
© 2026 四月 · CC BY-NC-SA 4.0
原文链接:https://www.aprilzz.com/tutorials/bash-dev-tcp-http-guide
相关文章
Bash4LLM⁺ 使用教程:用纯 Bash 脚本优雅调用 LLM API
Bash4LLM⁺ 是一个纯 Bash 编写的 LLM API 包装器,无需 Python/Node.js,单脚本即可调用 Groq 等提供商的 Chat Completions API
用 Caddy + PM2 自托管 Next.js 应用到 VPS:完整部署指南
手把手教你用 Caddy(自动 HTTPS 反向代理)和 PM2(进程守护)在 VPS 上部署 Next.js 应用,从零开始的完整教程。
2026 年用 AI 开发 App 完整实战指南:从想法到上线的 8 步工作流
一套完整的 AI 辅助 App 开发方法论:从定义问题、选择工具、架构先行、增量构建到代码审查和部署上线的 8 个实战步骤,附每步的具体操作指南和常见陷阱。