fix(deploy): 交互提示写不进终端导致静默退出——fd 3 必须用 <> 打开,不能只读

Ubuntu 24.04 实机定位:菜单打印完就退回 shell、退出码 1、没有任何提示(用户三次复现)。
根因是 `exec 3</dev/tty` 以**只读**方式打开 fd 3,而 read_prompt / read_secret 要往它**写**提示语:

  $ exec 3<file; printf x >&3
  bash: printf: write error: Bad file descriptor      # 退出码 1

在 set -e 下这一下就把脚本打死;错误信息还因为走 tee 管道、脚本退出时进程替换的子进程来不及
flush 而被吞掉 —— 于是表现成"菜单打完就默默消失"。

- install.sh 的 open_prompt_fd、classhelper 的 open_tty 都改成 `exec 3<>/dev/tty`(读写打开);
- 提示语写失败改为显式降级(该项用默认值 + 打印一条提示),不再让整条命令消失;
- classhelper 里是同一份实现,不修的话 Linux 上 `classhelper password` 会同样毫无提示地退出。

本地复现验证过三种情形:只读 fd 写 → Bad file descriptor / 退出码 1;`<>` 写 → 成功;
`if printf … >&3; then` 里的失败不会被 set -e 打死(新加的护栏确实兜住了)。
This commit is contained in:
LoveChengke committed 2026-10-05 19:50:24 +08:00
1 parent f6edfa5ea1
commit 2be8d30376
2 files changed
+30 -11

No files matched your search

+12 -4
View File
@@ -348,7 +348,11 @@ PROMPT_FD=""
open_prompt_fd() {
if [ -n "$PROMPT_FD" ]; then return 0; fi
if [ "${CLASSHELPER_NO_TTY:-0}" = "1" ]; then return 1; fi
if exec 3</dev/tty 2>/dev/null; then PROMPT_FD=3; return 0; fi
# 必须用 `<>`(读写)打开,不能只用 `<`(只读):下面 read_prompt 要往 fd 3 **写**提示语,
# 而往只读 fd 写会 EBADF("printf: write error: Bad file descriptor"),在 set -e 下
# 直接把安装脚本打死 —— 现象是"菜单打印完就默默退回 shell",错误还被 tee 吞掉,极难查。
# 2026-10-05 在 Ubuntu 24.04 上实测踩到并定位。
if exec 3<>/dev/tty 2>/dev/null; then PROMPT_FD=3; return 0; fi
return 1
}
@@ -362,9 +366,13 @@ read_prompt() {
local prompt="$1" var="$2" hidden="${3:-0}" value=""
printf -v "$var" ''
if open_prompt_fd; then
printf '%s' "$prompt" >&3
if [ "$hidden" = "1" ]; then IFS= read -r -s value <&3 || true; else IFS= read -r value <&3 || true; fi
printf '\n' >&3
# 提示语写不进去时**不能让它把脚本打死**:退化成"这一项用默认值",并且把这件事说出来
if printf '%s' "$prompt" >&3 2>/dev/null; then
if [ "$hidden" = "1" ]; then IFS= read -r -s value <&3 || true; else IFS= read -r value <&3 || true; fi
printf '\n' >&3 2>/dev/null || true
else
fail_print "$(printf '%s[classhelper]%s 写不进终端(fd 3 不可写),这一项按默认值处理' "$C_YELLOW" "$C_RESET")"
fi
fi
# 刻意**不**退回 stdin:`curl … | bash` 时 stdin 就是脚本内容本身,
# 从 stdin 读会把脚本的下一行当成用户输入吃掉,表现为"脚本后半段莫名其妙乱套"。