【问题标题】:Can't close an SSH connection opened with Popen无法关闭使用 Popen 打开的 SSH 连接
【发布时间】:2018-04-06 16:28:21
【问题描述】:

我创建了一个类方法(仅在 Linux 上运行),它通过 SSH 向远程计算机发送命令列表并使用 subprocess.Popen 返回输出:

def remoteConnection(self, list_of_remote_commands):
    ssh = subprocess.Popen(["ssh", self.ssh_connection_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,universal_newlines=True, bufsize=0)

    # send ssh commands to stdin
    for command in list_of_remote_commands:
        ssh.stdin.write(command + "\n")
        ssh.stdin.close()

    output_dict = {'stdin': list(ssh.stdin), 'stdout': list(ssh.stdout), 'stderr': list(ssh.stderr)}

    return output_dict

虽然我仍在处理子进程模块,但我读过很多关于 Popen 的文章,但没有人提到关闭它(SSH Connection with Python 3.0Proper way to close all files after subprocess Popen and communicatehttps://docs.python.org/2/library/subprocess.html)所以我认为那是没问题。

但是,当在函数之外的 ipython 中对此进行测试时,我注意到变量 ssh 似乎仍然处于活动状态。我尝试关闭 ssh.stdin、ssh.stdout 和 ssh.stderr,甚至 ssh.close()、ssh.terminate() 和 ssh.kill(),但似乎没有什么可以关闭它。我想也许没关系,但我的函数会在几个月甚至几年内被多次调用,所以我不希望它每次运行时都产生一个新进程,否则我将很快用完我的最大进程限制.所以我使用 ssh.pid 来查找 PID 并使用 ps aux | grep PID 查找它,即使在完成上述所有操作后它仍然存在。

我也试过了:

with subprocess.Popen(["ssh", self.ssh_connection_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,universal_newlines=True, bufsize=0) as shh:

代替:

ssh = subprocess.Popen(["ssh", self.ssh_connection_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,universal_newlines=True, bufsize=0)

我还记得不久前使用ssh -T 解决了类似的问题,但甚至:

ssh = subprocess.Popen(["ssh", "-T",  self.ssh_connection_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,universal_newlines=True, bufsize=0)

没用。

如果我需要的话,我确信我会找到一些关于关闭 Popen 的信息,但是为什么该进程仍然在我的计算机上打开 - 谁能帮助我了解这里发生了什么?

【问题讨论】:

  • 打开sshpopen 的连接听起来是个坏习惯,您是否查看过ssh_decorateparamiko

标签: python python-3.x ssh


【解决方案1】:

在您的情况下,您在这里遇到了死锁:

output_dict = {'stdin': list(ssh.stdin), 'stdout': list(ssh.stdout), 'stderr': list(ssh.stderr)}

主要是因为list(ssh.stdin) 永远阻塞:尝试读取进程的标准输入 不起作用(还有额外的风险,因为您在不使用线程的情况下将标准输出和错误都重定向到不同的管道消费它们)

您的意思是使用ssh.communicate,将整个输入作为参数传递。只需这样做:

command_input = "".join(["{}\n".format(x) for x in list_of_remote_commands])
output,error = ssh.communicate(command_input)  # may need .encode() for python 3
return_code = ssh.wait()

然后

output_dict = {'stdin': list_of_commands, 'stdout': output.splitlines(), 'stderr': error.splitlines()}

我可以补充一点,在特定的 ssh 情况下,使用 paramiko 模块更好 (python paramiko ssh) 并完全避免使用 subprocess

【讨论】:

  • ssh.communicate 命令抛出错误:AttributeError: 'list' object has no attribute 'encode' 也许您的意思是:command_input = "\n".join(list_of_remote_commands) + "\n"
最近更新 更多