使用 Paramiko 在 Python 中通过 ssh 实现交互式 shell?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35821184/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 17:00:34  来源:igfitidea点击:

Implement an interactive shell over ssh in Python using Paramiko?

pythonshellsshparamikointeractive

提问by misha

I want to write a program (in Python 3.x on Windows 7) that executes multiple commands on a remote shell via ssh. After looking at paramikos' exec_command()function, I realized it's not suitable for my use case (because the channel gets closed after the command is executed), as the commands depend on environment variables (set by prior commands) and can't be concatenated into one exec_command()call as they are to be executed at different times in the program.

我想编写一个程序(在 Windows 7 上的 Python 3.x 中),它通过 ssh 在远程 shell 上执行多个命令。查看 paramikos 的exec_command()函数后,我意识到它不适合我的用例(因为在命令执行后通道会关闭),因为命令依赖于环境变量(由先前的命令设置)并且不能连接成一个exec_command()调用它们将在程序中的不同时间执行。

Thus, I want to execute commands in the same channel. The next option I looked into was implementing an interactive shell using paramikos' invoke_shell()function:

因此,我想在同一频道中执行命令。我研究的下一个选项是使用 paramikosinvoke_shell()函数实现交互式 shell :

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host, username=user, password=psw, port=22)

channel = ssh.invoke_shell()

out = channel.recv(9999)

channel.send('cd mivne_final\n')
channel.send('ls\n')

while not channel.recv_ready():
    time.sleep(3)

out = channel.recv(9999)
print(out.decode("ascii"))

channel.send('cd ..\n')
channel.send('cd or_fail\n')
channel.send('ls\n')

while not channel.recv_ready():
    time.sleep(3)

out = channel.recv(9999)
print(out.decode("ascii"))

channel.send('cd ..\n')
channel.send('cd simulator\n')
channel.send('ls\n')

while not channel.recv_ready():
    time.sleep(3)

out = channel.recv(9999)
print(out.decode("ascii"))

ssh.close() 

There are some problems with this code:

这段代码有一些问题:

  1. The first printdoesn't always print the lsoutput (sometimes it is only printed on the second print).
  2. The first cdand lscommands are always present in the output (I get them via the recvcommand, as part of the output), while all the following cdand lscommands are printed sometimes, and sometimes they aren't.
  3. The second and third cdand lscommands (when printed) always appear before the first lsoutput.
  1. 第一个print并不总是打印ls输出(有时它只打印在第二个上print)。
  2. 第一个cdls命令总是出现在输出中(我通过recv命令获取它们,作为输出的一部分),而所有以下cdls命令有时会打印,有时不会。
  3. 第二个和第三个cdandls命令(打印时)总是出现在第一个ls输出之前。

I'm confused with this "non-determinism" and would very much appreciate your help.

我对这种“非确定性”感到困惑,非常感谢您的帮助。

回答by misha

import paramiko
import re


class ShellHandler:

    def __init__(self, host, user, psw):
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.ssh.connect(host, username=user, password=psw, port=22)

        channel = self.ssh.invoke_shell()
        self.stdin = channel.makefile('wb')
        self.stdout = channel.makefile('r')

    def __del__(self):
        self.ssh.close()

    def execute(self, cmd):
        """

        :param cmd: the command to be executed on the remote computer
        :examples:  execute('ls')
                    execute('finger')
                    execute('cd folder_name')
        """
        cmd = cmd.strip('\n')
        self.stdin.write(cmd + '\n')
        finish = 'end of stdOUT buffer. finished with exit status'
        echo_cmd = 'echo {} $?'.format(finish)
        self.stdin.write(echo_cmd + '\n')
        shin = self.stdin
        self.stdin.flush()

        shout = []
        sherr = []
        exit_status = 0
        for line in self.stdout:
            if str(line).startswith(cmd) or str(line).startswith(echo_cmd):
                # up for now filled with shell junk from stdin
                shout = []
            elif str(line).startswith(finish):
                # our finish command ends with the exit status
                exit_status = int(str(line).rsplit(maxsplit=1)[1])
                if exit_status:
                    # stderr is combined with stdout.
                    # thus, swap sherr with shout in a case of failure.
                    sherr = shout
                    shout = []
                break
            else:
                # get rid of 'coloring and formatting' special characters
                shout.append(re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]').sub('', line).
                             replace('\b', '').replace('\r', ''))

        # first and last lines of shout/sherr contain a prompt
        if shout and echo_cmd in shout[-1]:
            shout.pop()
        if shout and cmd in shout[0]:
            shout.pop(0)
        if sherr and echo_cmd in sherr[-1]:
            sherr.pop()
        if sherr and cmd in sherr[0]:
            sherr.pop(0)

        return shin, shout, sherr