【问题标题】:The right way to close ssh connection when using pexpect?使用 pexpect 时关闭 ssh 连接的正确方法?
【发布时间】:2018-01-29 13:14:28
【问题描述】:

我使用 Python pexpect(和 winpexpect)通过 ssh 执行命令

if platform.system() == 'Windows':
    pexpectmodname = "winpexpect"
    from winpexpect import winspawn as spawn
else:
    pexpectmodname = "pexpect"
    from pexpect import spawn

pexpectmod = __import__(pexpectmodname)

...

# connect
shellCmd = "ssh %s %d -l %s %s" % (portParam, port, username, server.host)
self.spawn = spawn(shellCmd, timeout=self.timeout, env=os.environ, ignore_sighup=False)

...
# DO WORK WITH SSH
...

# close ssh connection #1 (send exit)
self.spawn.sendline('exit')
index = self.spawn.expect([pexpectmod.EOF, "(?i)there are stopped jobs"])
if index == 1:
    self.spawn.sendline("exit")
    self.spawn.expect([pexpect.EOF])

# close ssh connection #2 (check isalive, send exit and close)
if self.spawn.isalive():
    self.spawn.sendline('exit')
    self.spawn.close()

# close ssh connection #3 (send sigkill)
import signal
self.spawn.kill(signal.SIGKILL)

如何关闭 self.spawn 以确保 ssh 会话已关闭? Windows 和 UNIX 的跨平台方式是什么?

【问题讨论】:

    标签: python ssh pexpect winpexpect


    【解决方案1】:

    优雅的方式是发送exit\rCTRL-D 并等待EOF。但也可以在完成 SSH 会话后简单地退出脚本(Expect、pexpect 或 winexpect),而不必关心 SSH 会话的退出状态。当脚本退出时(不管它是否干净),操作系统内核将为您关闭 SSH 会话(TCP 连接)。 SSH 服务器不会抱怨这一点。

    pexpect.spawn.close() 将关闭 pty,而 pty 又会将SIGHUP 发送到 shell,这反过来会终止(杀死)shell,除非 shell 忽略 SIGHUP。这就像您在 shell 仍在运行时关闭了 PuTTY(或 gnome-terminal,...)窗口。

    更新:

    以上陈述假设所有应用程序(Expect、pexpect、winexpect 和 SSH 服务器)都已很好地实现。我有一个系统 (ESXi),当 SSH 连接未完全关闭时,SSH 服务器 未能回收分配的 PTY。所以一段时间后,尽管 SSH 身份验证成功,但我无法再获得 PTY。

    【讨论】:

    • 谢谢!我问这个问题是因为当我的脚本失败时我失去了很多 ssh 连接。我找到了这个主题github.com/pexpect/pexpect/issues/136,它对我有帮助,但我想知道“使用 pexpect 关闭 ssh 连接的正确方法”。
    • 我知道发送'exit'很好,但我不明白我是否需要在它之后调用self.spawn.close()?
    • .close() 将关闭 pty ,然后将 SIGHUP 发送到外壳,外壳又将终止(杀死)外壳。这就像您在 shell 仍在运行时关闭 PuTTY(或 gnome-terminal,...)窗口。
    【解决方案2】:

    如果你的目标只是通过 SSH 执行命令,你也可以考虑使用Paramiko

    import paramiko
    
    # Create a new client
    client = paramiko.SSHClient()
    # Connect to your remote
    client.connect('127.0.0.1', username='john', password='doe')
    
    # Execute a command
    stdin, stdout, stderr = client.exec_command("pwd")
    
    # Do anything you want with `stdout` and `stderr`
    # Execute as many other commands as you want
    
    # When you are done, close your connection
    client.close()
    

    它是一个纯 Python 包,无论何时使用它都运行良好。

    【讨论】: