如何在Linux中让控制台命令脱离TTY运行?FIFO输出无结果问题
脚本无输出的根源
- FIFO打开死锁:POSIX标准规定,FIFO以只写模式打开时,必须等有进程以只读模式打开对应管道才会解除阻塞;反过来,只读模式打开时,也必须等有只写模式的进程打开才会解除阻塞。你的脚本中,新会话启动的目标进程会先依次打开两个FIFO的写端,当前脚本却延迟打开FIFO的读端,当两端的open操作几乎同时发起时,会陷入互相等待的死锁——目标进程会一直卡在open()系统调用上,根本没机会执行实际命令,自然没有任何输出。
- 输出逻辑缺同步:脚本用后台
cat读stderr管道,前台cat读stdout管道,读完前台的stdout就直接清理临时文件,完全没等后台的stderr读取进程处理完所有输出,会导致stderr内容丢失。 - 输入通道未切断:目标进程的stdin没有重定向到
/dev/null,如果命令尝试直接从标准输入读数据,依然会继承原终端的输入流,没有完全实现“阻止命令自主访问TTY”的目标。
修正后的包装脚本
针对上述问题调整逻辑,核心改动是提前以读写模式打开FIFO破除死锁、等待所有读取进程完成后再清理、彻底切断目标进程和原终端的输入关联:
#!/bin/sh temp=$(mktemp -d) # 创建两个通信管道 mkfifo "$temp/out" "$temp/err" # 提前以读写模式打开管道,此时管道已经持有读写端,后续所有打开操作都不会阻塞,直接破除open死锁 exec 3<>"$temp/out" exec 4<>"$temp/err" # 写入待执行的命令 echo "$@" > "$temp/run.sh" # 启动无控制终端的目标进程:stdin指向/dev/null,stdout/stderr绑定到对应管道 setsid sh -c "sh '$temp/run.sh' </dev/null >'$temp/out' 2>'$temp/err'" & # 后台读取stderr内容,原样输出到当前终端的stderr cat <&4 1>&2 & err_cat_pid=$! # 前台读取stdout内容,原样输出到当前终端的stdout cat <&3 out_cat_pid=$! # 等待两个读取进程全部完成,避免丢失输出 wait "$out_cat_pid" "$err_cat_pid" # 关闭管道描述符,清理临时资源 exec 3<&- 4<&- rm -f "$temp/out" "$temp/err" "$temp/run.sh" rmdir "$temp"
更可靠的极简实现
完全不需要手动维护临时文件和FIFO,Linux原生工具已经可以实现所有需求:
#!/bin/sh setsid sh -c "$*" </dev/null 2>&1 | cat
这个实现的效果完全匹配你的目标:
- 目标进程在全新的会话中运行,没有控制终端。一旦命令尝试访问TTY索要输入(比如
sudo要密码、ssh要确认主机),打开/dev/tty的操作会直接返回错误,进程会立即失败退出,不会卡在交互等待状态。 - 所有stdout/stderr输出会实时转发到当前终端,没有手动管理FIFO带来的死锁、丢输出问题。
- 没有临时文件操作,不会残留垃圾文件,脚本逻辑极简。
核心原理:
setsid的作用就是创建全新的独立会话,新会话默认没有绑定控制终端,所有尝试访问控制终端(也就是TTY设备)的操作都会直接报错,这正好对应你“阻止命令自主访问TTY、遇到交互直接失败”的需求,不需要额外做复杂的拦截逻辑。
内容的提问来源于stack exchange,提问作者ternary
相关产品推荐
相关产品推荐

