使用Bash网络重定向时读取文件描述符3为何会挂起?
我正在用Bash和网络重定向发送HTTP请求,脚本如下:
exec 3<> $initial_url path="/webhdfs/v1$hdfs_target?user.name=hdfs&op=OPEN" printf "GET $path HTTP/1.1 Host: $url:$port Accept: */* " >&3 echo toto cat <&3 echo titi exec 3<&
我已经正确接收到响应,但cat命令无限挂起。执行输出(通过Ctrl+C终止)如下:
$ time bash toto.sh toto HTTP/1.1 307 TEMPORARY_REDIRECT Cache-Control: no-cache Expires: Thu, 20 Jul 2023 12:29:41 GMT Date: Thu, 20 Jul 2023 12:29:41 GMT Pragma: no-cache Expires: Thu, 20 Jul 2023 12:29:41 GMT Date: Thu, 20 Jul 2023 12:29:41 GMT Pragma: no-cache X-FRAME-OPTIONS: SAMEORIGIN Set-Cookie: hadoop.auth="redacted_for_SO"; Path=/; HttpOnly Location: redacted_for_SO Content-Type: application/octet-stream Content-Length: 0 real 0m19.042s user 0m0.124s sys 0m0.733s
输出里已经包含了我需要的所有内容,但cat就是无法结束。请问:
- 为什么
cat会挂起?它在等待什么? - 如何在数据接收完成后避免其挂起?
问题解答
1. cat挂起的原因
cat <&3挂起的核心原因是:你通过exec 3<> $initial_url建立的是双向TCP长连接。HTTP/1.1默认启用Keep-Alive,服务器在返回完整响应(本次响应的Content-Length: 0说明没有响应体)后,不会主动关闭连接,而是保持连接等待后续请求复用。
而cat的逻辑是持续从指定文件描述符读取数据,直到遇到EOF(文件结束符)。由于服务器没关闭连接,文件描述符3的读端一直处于可读状态(没有EOF),所以cat会一直阻塞等待新数据,直到你手动终止脚本。
另外脚本最后一行的exec 3<&是语法错误,正确关闭双向连接的写法是exec 3<&- 3>&-,但这个错误不是导致挂起的直接原因——挂起发生在cat执行阶段,此时连接还未被处理。
2. 避免挂起的解决方法
方案一:强制关闭长连接
在HTTP请求头中添加Connection: close,明确告诉服务器返回响应后立即关闭连接。这样服务器发送完响应后会主动断开TCP连接,cat读取完所有数据后会收到EOF,自动退出:
exec 3<> $initial_url path="/webhdfs/v1$hdfs_target?user.name=hdfs&op=OPEN" printf "GET $path HTTP/1.1 Host: $url:$port Accept: */* Connection: close " >&3 echo toto cat <&3 echo titi exec 3<&- 3>&-
方案二:按需读取并主动关闭连接
如果需要保留长连接,可在读取完所需响应内容后,手动关闭文件描述符的读端。比如可以用awk读取到响应头结束的空行后退出,再关闭连接:
exec 3<> $initial_url path="/webhdfs/v1$hdfs_target?user.name=hdfs&op=OPEN" printf "GET $path HTTP/1.1 Host: $url:$port Accept: */* " >&3 echo toto # 读取到空行为止(响应头结束) awk '/^$/ {exit} {print}' <&3 echo titi exec 3<&- 3>&-
方案三:使用curl替代(更简洁可靠)
如果没有必须用Bash原生重定向的需求,直接用curl工具更省心——它会自动处理HTTP连接的建立、响应读取和连接关闭,无需手动管理文件描述符:
curl "http://$url:$port/webhdfs/v1$hdfs_target?user.name=hdfs&op=OPEN"
内容的提问来源于stack exchange,提问作者Itération 122442

