问题 使用python的pty创建一个实时控制台


我正在尝试创建一个将在服务器上远程执行的执行环境/ shell,它将stdout,err,流入套接字以在浏览器中呈现。我目前已经尝试过使用的方法 subprocess.run 用一个 PIPE。问题是我在进程完成后得到了stdout。我想要实现的是获得逐行,伪终端的实现。

我目前的实施

test.py

def greeter():
    for _ in range(10):
        print('hello world')

greeter()

并在壳中

>>> import subprocess
>>> result = subprocess.run(['python3', 'test.py'], stdout=subprocess.PIPE)
>>> print(result.stdout.decode('utf-8'))
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world

如果我尝试用这个简单的实现尝试 pty,怎么做呢?


7955
2017-08-13 20:27


起源

看一下这个: stackoverflow.com/questions/1606795/... - Dhruv Aggarwal
尝试使用 bufsize=1 参数到子进程设置行缓冲区,并使用 iter(result.stdout.readline, b'') 在True循环中读取包含的标准输出 - Chen A.


答案:


我确定在某个地方有一个骗局,但我很快就找不到它

process = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE,bufsize=0)

for out in iter(process.stdout.readline, b''):
    print(out)

4
2017-08-13 21:14



这还是会等待的 cmd 完成然后for循环将开始。我想要更多的异步实现,这就是为什么我想了解更多 pty - Ishan Khare
这应该实时流式传输...如果您没有发现这种情况,您可能希望将buffersize设置为零 - Joran Beasley
@IshanKhare>这个 将 实时流。该 Popen 函数在后台启动程序并立即返回。程序输出的任何内容都将立即被读取。请注意,读取是缓冲的,因此一旦读取了足够大的块,读取将返回(这就是为什么如果您使用太简单的示例进行测试,您可以认为它等待)。您可以使用禁用缓冲 bufsize=0如果你真的想要以牺牲性能为代价来完全实时读取。 - spectras


如果您的应用程序将与多个任务异步工作,例如从stdout读取数据然后将其写入websocket,我建议使用 ASYNCIO

下面是一个运行进程并将其输出重定向到websocket的示例:

import asyncio.subprocess
import os

from aiohttp.web import (Application, Response, WebSocketResponse, WSMsgType,
                         run_app)


async def on_websocket(request):
    # Prepare aiohttp's websocket...
    resp = WebSocketResponse()
    await resp.prepare(request)
    # ... and store in a global dictionary so it can be closed on shutdown
    request.app['sockets'].append(resp)

    process = await asyncio.create_subprocess_exec(sys.executable,
                                                   '/tmp/test.py',
                                                    stdout=asyncio.subprocess.PIPE,
                                                    stderr=asyncio.subprocess.PIPE,
                                                    bufsize=0)
    # Schedule reading from stdout and stderr as asynchronous tasks.
    stdout_f = asyncio.ensure_future(p.stdout.readline())
    stderr_f = asyncio.ensure_future(p.stderr.readline())

    # returncode will be set upon process's termination.
    while p.returncode is None:
        # Wait for a line in either stdout or stderr.
        await asyncio.wait((stdout_f, stderr_f), return_when=asyncio.FIRST_COMPLETED)

        # If task is done, then line is available.
        if stdout_f.done():
            line = stdout_f.result().encode()
            stdout_f = asyncio.ensure_future(p.stdout.readline())
            await ws.send_str(f'stdout: {line}')

        if stderr_f.done():
            line = stderr_f.result().encode()
            stderr_f = asyncio.ensure_future(p.stderr.readline())
            await ws.send_str(f'stderr: {line}')

    return resp


async def on_shutdown(app):
    for ws in app['sockets']:
        await ws.close()    


async def init(loop):
    app = Application()
    app['sockets'] = []
    app.router.add_get('/', on_websocket)
    app.on_shutdown.append(on_shutdown)
    return app


loop = asyncio.get_event_loop()
app = loop.run_until_complete(init())
run_app(app)

它用 aiohttp 并以此为基础 web_ws 和 子流程流 例子。


4
2017-08-26 06:28





如果你在Windows上,那么你将在很长一段时间内进行艰苦的战斗,我很抱歉你会忍受痛苦(去过那里)。但是,如果您使用的是Linux,则可以使用 Pexpect的 模块。 Pexpect允许您生成一个后台子进程,您可以使用它进行双向通信。这对所有类型的系统自动化都很有用,但一个非常常见的用例是ssh。

import pexpect

child   = pexpect.spawn('python3 test.py')
message = 'hello world'

while True:
    try:
        child.expect(message)
    except pexpect.exceptions.EOF:
        break
    input('child sent: "%s"\nHit enter to continue: ' %
         (message + child.before.decode()))

print('reached end of file!')

我发现创建一个类来处理像ssh连接这样复杂的东西非常有用,但是如果你的用例很简单,可能不合适或不必要。 pexpect.before的类型是字节,省略了你要搜索的模式可能很尴尬,所以创建一个至少为你处理这个问题的函数是有意义的。

def get_output(child, message):
    return(message + child.before.decode())

如果要将消息发送到子进程,可以使用child.sendline(line)。有关更多详细信息,请查看我链接的文档。

我希望我能帮忙!


2
2017-08-23 14:50





我不知道你是否可以在浏览器中渲染它,但你可以运行像模块这样的程序,所以你可以像这样立即得到stdout:

import importlib
from importlib.machinery import SourceFileLoader

class Program:

    def __init__(self, path, name=''):
        self.path = path
        self.name = name
        if self.path:
            if not self.name:
                self.get_name()
            self.loader = importlib.machinery.SourceFileLoader(self.name, self.path)
            self.spec = importlib.util.spec_from_loader(self.loader.name, self.loader)
            self.mod = importlib.util.module_from_spec(self.spec)
        return

    def get_name(self):
        extension = '.py' #change this if self.path is not python program with extension .py
        self.name = self.path.split('\\')[-1].strip('.py')
        return

    def load(self):
        self.check()
        self.loader.exec_module(self.mod)
        return

    def check(self):
        if not self.path:
            Error('self.file is NOT defined.'.format(path)).throw()
        return

file_path = 'C:\\Users\\RICHGang\\Documents\\projects\\stackoverflow\\ptyconsole\\test.py'
file_name = 'test'
prog = Program(file_path, file_name)   
prog.load()

您可以在test.py中添加sleep以查看差异:

from time import sleep

def greeter():
    for i in range(10):
        sleep(0.3)
        print('hello world')

greeter()

1
2017-08-24 18:29