2017-08-13 88 views
11

Próbuję utworzyć środowisko wykonawcze/powłokę, które będzie zdalnie uruchamiane na serwerze, który przesyła stdout, err, przez gniazdo do renderowania w przeglądarce. Wcześniej próbowałem podejścia do korzystania z subprocess.run z PIPE. Problem polega na tym, że dostaję standardowe wyjście po zakończeniu procesu. To, co chcę osiągnąć, to uzyskać pseudo-końcowy rodzaj implementacji typu "linia po wierszu".Użyj Pythona, aby utworzyć konsolę na żywo.

Moja obecna implementacja

test.py

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

greeter() 

i w powłoce

>>> 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 

jeśli staram się próbować nawet tę prostą implementację z pty, w jaki sposób jeden to zrobić?

+0

Sprawdź to: https://stackoverflow.com/questions/1606795/catching-stdout-in-realtime-from-subprocess –

+0

Spróbuj użyć parametru 'bufsize = 1' do podprocesu do ustawienia bufora linii i użyj' iter (result.stdout.readline, b '') 'aby przeczytać stdout owinięty podczas True loop – Vinny

Odpowiedz

4

Im na pewno tam dupe gdzieś, ale nie mogłem go znaleźć szybko

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

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

, to nadal czekałoby na zakończenie' cmd', a następnie uruchomi się pętla for. Chcę więcej asynchronicznej implementacji, dlatego chcę wiedzieć więcej o 'pty' –

+0

, to powinno przesyłać strumieniowo w czasie rzeczywistym ... możesz chcieć ustawić buforowanie na zero, jeśli nie znajdziesz tego w tym przypadku. –

+3

@ IshanKhare> to ** będzie ** streamować w czasie rzeczywistym. Funkcja 'Popen' uruchamia program w tle i natychmiast wraca. Wszystko, co wyjdzie z programu, zostanie natychmiast odczytane. Zauważ, że odczyty są buforowane, więc odczyty wrócą po przeczytaniu wystarczająco dużej porcji (dlatego jeśli testujesz za pomocą zbyt prostych przykładów, możesz pomyśleć, że czeka). Możesz wyłączyć buforowanie z 'bufsize = 0', jeśli naprawdę chcesz w pełni w czasie rzeczywistym odczytać kosztem wydajności. – spectras

2

Jeśli jesteś na systemie Windows, a następnie będzie walczyć bitwę na bardzo długi czas, a ja przepraszam za ból będziecie znosić (byli tam). Jeśli korzystasz z systemu Linux, możesz użyć modułu pexpect. Pexpect umożliwia odrodzenie procesu potomnego tła, z którym można przeprowadzić dwukierunkową komunikację. Jest to przydatne w przypadku wszystkich rodzajów automatyzacji systemu, ale bardzo częstym przypadkiem użycia jest 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!') 

Znalazłem to bardzo przydatne, aby utworzyć klasę do obsługi coś skomplikowane jak ssh, ale jeśli twój przypadek użycia jest dość prosta, że ​​może nie być właściwe i konieczne. Sposób, w jaki pexpect.before jest typu bajtów i pomija poszukiwany wzorzec, może być niezręczny, więc może być sens tworzenia funkcji, która będzie dla Ciebie obsługiwała co najmniej.

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

Jeśli chcesz wysyłać wiadomości do procesu potomnego, można użyć child.sendline (linia). Aby uzyskać więcej informacji, zapoznaj się z dokumentacją, którą połączyłem.

Mam nadzieję, że udało mi się pomóc!

1

Nie wiem, czy można to uczynić w przeglądarce, ale można uruchomić program jak moduł więc masz stdout natychmiast tak:

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() 

Można dodać do snu w test.py zobaczyć różnicę:

from time import sleep 

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

greeter() 
4

Jeśli aplikacja będzie działać asynchronicznie z wielu zadań, takich jak czytanie danych z stdout i następnie pisanie go do websocket, proponuję przy użyciu asyncio.

Poniżej przedstawiono przykład, w którym prowadzi się proces i przekierowuje jego wyjście w 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) 

wykorzystuje aiohttp i jest w oparciu o przykłady web_ws i subprocess streams.