教程·阅读约 4 分钟·
不用 curl 也能发 HTTP 请求:Bash 内置 /dev/TCP 网络编程指南

不用 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 把网络连接抽象成文件重定向。当你写:

code
exec 3<>/dev/tcp/example.com/80

Bash 在背后做了三件事:

  1. 调用 socket() 创建一个 TCP socket
  2. 调用 getaddrinfo() 解析 example.com 的 IP 地址
  3. 调用 connect() 建立 TCP 连接

然后把 socket 文件描述符(这里是 3)当作普通文件一样读写。你往 fd 3 写数据,就是发送网络包;从 fd 3 读数据,就是接收响应。

这就是 Bash 网络功能的全部秘密——没有魔法,只有抽象

实战一:发送 HTTP GET 请求

最简单的例子,从 Bash 直接发起 HTTP 请求:

code
#!/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)

code
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 写一个端口扫描器:

code
#!/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 服务是否在回应:

code
#!/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 也一样可以:

code
#!/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 -lsocat 等工具。纯 Bash 的 /dev/tcp客户端能力——它不能 listen(),只能 connect()

如果你想用纯 Bash 实现服务器端,需要借助 coproc 配合 ncsocat,但这已经偏离了"不依赖外部工具"的初衷。

实用技巧

超时控制

默认情况下,/dev/tcp 的连接会一直阻塞直到成功或超时(系统默认超时通常很长)。始终用 timeout 包装一下:

code
timeout 3 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null

错误处理

连接失败时,Bash 会把错误信息写到 stderr。捕获它:

code
if result=$(timeout 3 bash -c "echo >/dev/tcp/$host/$port" 2>&1); then
  echo "✅ 成功"
else
  echo "❌ 失败: $result"
fi

HTTP/1.0 模式

如果不需要持久连接,可以发 HTTP/1.0 请求(不需要 Host 头),但现代服务器(如 Nginx、Cloudflare)对 HTTP/1.0 的支持不完全一致。建议始终用 HTTP/1.1 + Connection: close

函数封装

把常用操作封装成函数,随时调用:

code
# 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——试试:

code
echo >/dev/tcp/google.com/80 && echo "网络通了"

如果返回成功提示,你的"Bash 网络工具箱"已经就绪了。

分享到
微博Twitter

© 2026 四月 · CC BY-NC-SA 4.0

原文链接:https://www.aprilzz.com/tutorials/bash-dev-tcp-http-guide