运行python中的ssh时出现“因stdin不是终端而无法分配伪终端”

4 投票
3 回答
10475 浏览
提问于 2025-04-16 09:48

我在Python里面运行ssh命令,没有使用像Paramiko这样的外部库。我这样做是有我的理由的。

简单来说,我是用这个命令:subprocess.Popen("ssh -t bla -- command")

但是执行这个命令时,我收到了以下信息:

Pseudo-terminal will not be allocated because stdin is not a terminal.

我加上-t的原因是,我希望当我结束我的Python脚本时,远程命令也能停止。

当我尝试使用-t -t(强制执行)时,我收到了以下信息:

tcgetattr: Inappropriate ioctl for device

有没有办法让我在Python中运行带有-t的ssh命令呢?

3 个回答

0

听起来你其实是在寻找一个和旧的 nohup 命令相反的东西;也就是说,你想要一种方法来确保远程命令能够响应当底层连接关闭时由 TTY(伪终端)驱动生成的 HUP(挂起)信号。

我个人觉得 Paramiko 比用 subprocess 模块来处理这个问题要好得多。你可以用 Paramiko 来打开连接,分配伪终端,并执行你的命令,过程非常简单;而且使用你现有的 known_hosts 和身份文件(私钥)的代码也相对直接。

这个食谱 使用 paramiko 通过 SSH 复制文件 在 ActiveState 上展示了如何加载 ssh 的 known_hosts,使用身份文件,以及与任何你可能正在运行的 ssh agent 进行交互。接下来,你想要的应该只是以下几个步骤:

你也可以使用 TwistedConch 来实现 SSH 协议。不过,理解 Twisted 编程框架可能会比较困难。

这里有一个简单的 Twisted SSH 服务器示例,支持伪终端:60 秒了解 Twisted Conch:PTY 请求。还有一个在你提问前一周的 SO 讨论 在 Twisted 中通过 SSH 运行远程命令的最佳方法?,但那并没有涉及到伪终端相关的问题。查看 twisted.conch.ssh.session 的文档,似乎它支持伪终端,但标注为“未文档化”。

当然,你也可以选择 Pexpect,启动一个本地的 ssh 客户端的 shell,并通过它发送你的远程命令。这种方式最接近你在自己 shell 中运行命令的方式。

1

如果你想在Python脚本被终止时也结束远程命令,你可以考虑一种替代方案,而不是使用-t选项来运行ssh。这个方法是使用一个命名管道,当sshd的标准输入被关闭时,把“结束文件(EOF)”转换成“挂起信号(SIGHUP)”。具体可以参考这个链接:Bug 396 - sshd在没有分配伪终端时会孤立进程

# sample code in Bash

# press ctrl-d for EOF
ssh localhost '
TUBE=/tmp/myfifo.fifo
rm -f "$TUBE"
mkfifo "$TUBE"
#exec 3<>"$TUBE"

<"$TUBE" sleep 100 &  appPID=$!

# cf. "OpenSSH and non-blocking mode", 
# http://lists.mindrot.org/pipermail/openssh-unix-dev/2005-July/023090.html
#cat >"$TUBE"
#socat -u STDIN "PIPE:$TUBE"
dd of="$TUBE" bs=1 2>/dev/null
#while IFS="" read -r -n 1 char; do printf '%s' "$char"; done > "$TUBE"

#kill -HUP -$appPID 
kill $appPID

rm -f "$TUBE"
'
2

我想建议你,可能你问的问题不太对,正确的问题应该是“我怎么确保当我的Python程序结束时,子进程也能结束”。

你可以做一些聪明的Unix操作,比如分配一个伪终端(pty),确保你的子进程使用这个作为标准输入输出等等,这样ssh命令就会觉得它是在和一个终端对话,而不是和一个管道对话,但这可能并不是你真正想要做的事情。

撰写回答