如何使用subprocess模块与SSH进行交互

5

我正在尝试使用子进程spawn一个ssh子进程。

我在Windows 7上使用Python 2.7.6进行开发。

这是我的代码:

from subprocess import *
r=Popen("ssh sshserver@localhost", stdout=PIPE)
stdout, stderr=r.communicate()
print(stdout)
print(stderr)

输出结果:
None

标准输出应包含: sshserver@localhost的密码:


1
如果你需要自动化ssh,你可能想要调查类似pexpect的工具。你也可以考虑使用基于密钥的身份验证,这样就不需要处理密码了。 - larsks
我已经使用过pexpect,但它在Windows上无法完美运行,因此我想使用标准的Python模块。@larsks - WannaBeGenius
@Torxed 我已经安装了Debian并使用pexpect工作,但我想找到一个Windows的解决方案:D - WannaBeGenius
不使用 paramiko 有什么原因吗? - Abhijit
那么,恐怕您需要使用基于密钥的身份验证。我也曾经大量尝试过这个问题,因为我们工作站以前使用的是Windows系统,后来才转向了“黑暗面”。总的来说,pexpect是唯一可以处理这种情况的“默认”Python库。我记得我花了好几天时间用Popen()来让它正常工作,但是你不能使用第三方SSH客户端。 - Torxed
显示剩余3条评论
1个回答

12
以下是一份可处理证书部分的SSH代码示例,同时也能处理yes/no提示和密码输入请求。
#!/usr/bin/python

import pty, sys
from subprocess import Popen, PIPE, STDOUT
from time import sleep
from os import fork, waitpid, execv, read, write

class ssh():
    def __init__(self, host, execute='echo "done" > /root/testing.txt', askpass=False, user='root', password=b'SuperSecurePassword'):
        self.exec = execute
        self.host = host
        self.user = user
        self.password = password
        self.askpass = askpass
        self.run()

    def run(self):
        command = [
                '/usr/bin/ssh',
                self.user+'@'+self.host,
                '-o', 'NumberOfPasswordPrompts=1',
                self.exec,
        ]

        # PID = 0 for child, and the PID of the child for the parent    
        pid, child_fd = pty.fork()

        if not pid: # Child process
            # Replace child process with our SSH process
            execv(command[0], command)

        ## if we havn't setup pub-key authentication
        ## we can loop for a password promt and "insert" the password.
        while self.askpass:
            try:
                output = read(child_fd, 1024).strip()
            except:
                break
            lower = output.lower()
            # Write the password
            if b'password:' in lower:
                write(child_fd, self.password + b'\n')
                break
            elif b'are you sure you want to continue connecting' in lower:
                # Adding key to known_hosts
                write(child_fd, b'yes\n')
            elif b'company privacy warning' in lower:
                pass # This is an understood message
            else:
                print('Error:',output)

        waitpid(pid, 0)

如果我说错了,请指正,你无法直接读取stdin的原因是因为SSH作为不同进程ID的子进程运行,需要读取/附加到该进程。

由于您正在使用Windows,pty将无法正常工作。有两个更好的解决方案,即pexpect和某人指出的基于密钥的身份验证。

为了实现基于密钥的身份验证,您只需要执行以下操作: 在客户端上运行:ssh-keygenid_rsa.pub内容(一行)复制到服务器上的/home/user/.ssh/authorized_keys中。

然后你就完成了。 如果没有,就使用pexpect。

import pexpect
child = pexpect.spawn('ssh user@host.com')
child.expect('Password:')
child.sendline('SuperSecretPassword')

pty模块需要termios,该模块仅适用于Unix平台。 - WannaBeGenius
我在我的回答中提到了,不是吗? :/ - Torxed
是的,你说对了,我也提到了我正在使用 Windows ;) - WannaBeGenius
这就是为什么我发布了另外两个在Windows上可行的解决方案;请注意:Linux的修复方法适用于其他任何人(因为这是一个常见问题,会有很多人来到这里),而且你现在也有了一台Linux机器,所以这也会对你有所帮助 :P - Torxed
你节省了与Windows斗争的时间,因为似乎还没有解决方案。 ;) - WannaBeGenius
3
您的答案中提供的解决方案在Windows上无法使用。 pexpect 需要 pty,而 pty 是针对Linux编写的,需要期望一个POSIX系统。 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接