运行python中的ssh时出现“因stdin不是终端而无法分配伪终端”
我在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 个回答
听起来你其实是在寻找一个和旧的 nohup 命令相反的东西;也就是说,你想要一种方法来确保远程命令能够响应当底层连接关闭时由 TTY(伪终端)驱动生成的 HUP(挂起)信号。
我个人觉得 Paramiko 比用 subprocess 模块来处理这个问题要好得多。你可以用 Paramiko 来打开连接,分配伪终端,并执行你的命令,过程非常简单;而且使用你现有的 known_hosts 和身份文件(私钥)的代码也相对直接。
这个食谱 使用 paramiko 通过 SSH 复制文件 在 ActiveState 上展示了如何加载 ssh 的 known_hosts,使用身份文件,以及与任何你可能正在运行的 ssh agent 进行交互。接下来,你想要的应该只是以下几个步骤:
- 创建一个会话,
- 创建一个通道,
- 获取一个伪终端,
- 然后 执行你的远程命令 或者 调用一个 shell,并通过它返回的类似 stdin 的对象发送你的命令。
你也可以使用 TwistedConch 来实现 SSH 协议。不过,理解 Twisted 编程框架可能会比较困难。
这里有一个简单的 Twisted SSH 服务器示例,支持伪终端:60 秒了解 Twisted Conch:PTY 请求。还有一个在你提问前一周的 SO 讨论 在 Twisted 中通过 SSH 运行远程命令的最佳方法?,但那并没有涉及到伪终端相关的问题。查看 twisted.conch.ssh.session 的文档,似乎它支持伪终端,但标注为“未文档化”。
当然,你也可以选择 Pexpect,启动一个本地的 ssh 客户端的 shell,并通过它发送你的远程命令。这种方式最接近你在自己 shell 中运行命令的方式。
如果你想在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"
'
我想建议你,可能你问的问题不太对,正确的问题应该是“我怎么确保当我的Python程序结束时,子进程也能结束”。
你可以做一些聪明的Unix操作,比如分配一个伪终端(pty),确保你的子进程使用这个作为标准输入输出等等,这样ssh命令就会觉得它是在和一个终端对话,而不是和一个管道对话,但这可能并不是你真正想要做的事情。