【问题标题】:Python ssh connection to/with interactive program (eg, python interpreter)Python ssh 连接到/与交互式程序(例如,python 解释器)
【发布时间】:2017-05-16 19:06:45
【问题描述】:

我想使用 python ssh 进入远程机器,在远程启动交互式可执行文件,然后与所述可执行文件交互。

换句话说,我想模仿当您执行以下操作时会发生什么:

ssh me@host -t python2

(python2 只是 python,但为了避免混淆,我给它起了不同的名字)。

当我执行上面的命令行时,我在远程与 python2 进行“无缝”交互。

我似乎无法在 python 中执行此操作。我尝试过使用 paramiko 和 subprocess,此时两者都有相同的问题。例如在子进程中:

ssh = subprocess.Popen("ssh me@host -t python2",
                       shell=True,
                       stdout=subprocess.PIPE,
                       stderr=subprocess.PIPE)
result = ssh.stdout.readlines()
if result == []:
    error = ssh.stderr.readlines()
    print >> sys.stderr, "ERROR: %s" % error
else:
    print result

当我这样做时,我在命令行上什么也看不到,但我可以通过ps 看到 python2 确实在运行,我只是无法以我想要的方式与之交互。我看不到 Python 的输出。

我希望能够从主机 python 脚本提交命令,例如 ssh.execute("1+1")。这基本上会告诉 python2 计算 1+1。然后我想将输出读回主机 python 脚本。

【问题讨论】:

    标签: python linux ssh


    【解决方案1】:

    问题

    您的问题是①ssh.stdout.readlines() 正在读取所有现有输入,这些输入已经缓冲到您正在访问它的点,并按行尾分隔(\r\n)。

    但是因为是打开提示,所以②可能只有一行,即>>>,没有行尾。

    处理流

    你需要做的是创建一个while循环来读取输出:

    def handle_output(out):
        while True:
            out_line = out.readline()
            print("OUT: {}".format(out_line))
    
    def handle_errput(err):
        while True:
            err_line = err.readline()
            print("ERR: {}".format(err_line))
    

    这样就可以了:

    handle_output(ssh.stdout)
    handle_errput(ssh.stderr)
    

    然后,一旦远程 python 解释器打印出完整的行,您的控制台上就会显示一些内容。但这只能解决①。

    您仍然需要让远程进程输出至少一整行,而这样做的唯一方法是实际向它发送数据!

    所以你想添加另一个功能:

    def handle_input(inp):
        while True:
            inp_line = sys.stdin.readline()
            inp.write(inp_line)
    

    你会这样称呼它:

    handle_input(ssh.stdin)
    

    这样就解决了②。

    并行读取

    带线程

    但是现在,如果你这样做了,从逻辑上讲,你还有另一个问题:

    handle_output(ssh.stdout)
    handle_errput(ssh.stderr)
    handle_input(ssh.stdin)
    

    您的程序流永远不会到达handle_errput()handle_input(),因为它会卡在handle_output() 无限循环中。

    解决方案是并行执行三个循环:

    out_thread = Thread(target=handle_output, args=(ssh.stdout,))
    err_thread = Thread(target=handle_errput, args=(ssh.stderr,))
    try:
        out_thread.start()
        err_thread.start()
        handle_input()
    finally:
        out_thread.join()
        err_thread.join()
    

    这样从外部进程读取将发生在另一个线程中,而写入将发生在当前线程中。您也可以启动三个线程(一个用于读取 stdout,一个用于读取 stderr,一个用于写入)并让主线程运行一个循环,该循环将执行“处理”并与这些线程通信(提示:使用 threading.Queue 进行交换线程之间的数据)。

    带有事件库(仅限 py3)

    如果您使用异步库like asyncio offered with python 3(对于 py2,您可以使用 tornado 或 greenlet),您可以避免使用多线程以及线程间通信的痛苦,这将为您提供一个非常好的抽象,而不是使用 select()

    async def handle_output(out):
        while True:
            await out_line = out.readline()
            print("OUT: {}".format(out_line))
    
    async def handle_errput(err):
        while True:
            await err_line = err.readline()
            print("ERR: {}".format(err_line))
    
    async def handle_input(inp):
        while True:
            await inp_line = sys.stdin.readline()
            inp.write(inp_line)
    
    asyncio.Task(handle_output(ssh.stdout))
    asyncio.Task(handle_errput(ssh.stderr))
    asyncio.Task(handle_input(ssh.stdin))
    
    asyncio.get_event_loop().run_forever()
    

    使用 select() 调用

    在 python 2 中,最好和最简单(但远非最简单)的实现方式是使用select()

    基本上一个选择调用会看起来像

    while True:
      readable, writable, excepts = select.select(
               [ssh.stdout, ssh.stderr], # outputs to watch
               [sys.stdin],              # inputs to watch
               [ssh.stdout, ssh.stderr], # exceptions to watch
               1)                        # timeout
      for r in readable:
        print(r.read())
      for w in writable:
        p.stdin.write(w.read())
    

    但你最好read a thorough documentation about select()

    从那里去哪里?

    我希望能够从主机 python 脚本提交命令,例如 ssh.execute("1+1")。这基本上会告诉 python2 计算 1+1。然后我想将输出读回主机 python 脚本。

    我给你的解决方案是帮助你处理从终端到远程 python 进程的透明通信。它暴露了实现上述所有工具所缺少的所有工具,除了一个,如何在 3 个循环之间进行通信?

    最简单的方法是使用三个队列,两个处理从远程进程传入的结果,最后一个处理传出命令:

    input_queue = queue.Queue()
    output_queue = queue.Queue()
    errput_queue = queue.Queue()
    

    然后在你的主线程中你可以实现:

    def remote_python_execute(command, output_queue, errput_queue):
        # send a command to the remote process
        input_queue.put(command)
        out, err = ([], [])
        # read the output and then the errput as long as
        # there's something to read
        while not output_queue.empty():
            out.append(output_queue.get())
        while not errput_queue.empty():
            err.append(errput_queue.get())
        return out, err
    

    你的 handle_output 函数看起来像(你可以适应两者:

    def handle_output(out, output_queue):
        while True:
            output_queue.put(out.readline())
    

    只是给你一个想法......现在轮到你填补空白并选择最适合你的需求?


    免责声明:此答案包含未经测试的代码,旨在为您提供有关如何实现所需目标的方向。不要复制/粘贴它,而是阅读文档并了解您在做什么。还要小心使用线程,作为提示,切勿在两个线程中使用变量,否则您可能会被咬。很难。


    HTH

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      • 2023-04-06
      • 1970-01-01
      • 1970-01-01
      • 2013-08-26
      • 1970-01-01
      • 2014-10-20
      相关资源
      最近更新 更多