2013-06-19 10 views
14

jest to możliwe, aby zmodyfikować kod poniżej mieć wydruk „standardowe wyjście” i «»: stderrsubprocess.Popen: Klonowanie i stderr zarówno standardowe wyjście do zacisku i zmienne

  • drukowane na terminalu(w czasie rzeczywistym czas),
  • i ostatecznie przechowywane w outach i errs zmiennych?

Kod:

#!/usr/bin/python3 
# -*- coding: utf-8 -*- 

import subprocess 

def run_cmd(command, cwd=None): 
    p = subprocess.Popen(command, cwd=cwd, shell=False, 
         stdout=subprocess.PIPE, 
         stderr=subprocess.PIPE) 
    outs, errs = p.communicate() 
    rc = p.returncode 
    outs = outs.decode('utf-8') 
    errs = errs.decode('utf-8') 

    return (rc, (outs, errs)) 

Dzięki @unutbu, specjalne podziękowania dla @ JF Sebastian, ostateczną funkcję:

#!/usr/bin/python3 
# -*- coding: utf-8 -*- 


import sys 
from queue import Queue 
from subprocess import PIPE, Popen 
from threading import Thread 


def read_output(pipe, funcs): 
    for line in iter(pipe.readline, b''): 
     for func in funcs: 
      func(line.decode('utf-8')) 
    pipe.close() 


def write_output(get): 
    for line in iter(get, None): 
     sys.stdout.write(line) 


def run_cmd(command, cwd=None, passthrough=True): 
    outs, errs = None, None 

    proc = Popen(
     command, 
     cwd=cwd, 
     shell=False, 
     close_fds=True, 
     stdout=PIPE, 
     stderr=PIPE, 
     bufsize=1 
     ) 

    if passthrough: 

     outs, errs = [], [] 

     q = Queue() 

     stdout_thread = Thread(
      target=read_output, args=(proc.stdout, [q.put, outs.append]) 
      ) 

     stderr_thread = Thread(
      target=read_output, args=(proc.stderr, [q.put, errs.append]) 
      ) 

     writer_thread = Thread(
      target=write_output, args=(q.get,) 
      ) 

     for t in (stdout_thread, stderr_thread, writer_thread): 
      t.daemon = True 
      t.start() 

     proc.wait() 

     for t in (stdout_thread, stderr_thread): 
      t.join() 

     q.put(None) 

     outs = ' '.join(outs) 
     errs = ' '.join(errs) 

    else: 

     outs, errs = proc.communicate() 
     outs = '' if outs == None else outs.decode('utf-8') 
     errs = '' if errs == None else errs.decode('utf-8') 

    rc = proc.returncode 

    return (rc, (outs, errs)) 
+0

Przykładowy kod sklep 'outs' i 'errs' i zwraca je ... Aby wydrukować do terminala, po prostu' jeśli out: print out' 'if errs: print errs' – bnlucas

+2

@ bnlucas Dzięki, ale jak stwierdziłem w pierwszym punkcie: dane wyjściowe powinny być wydrukowane w PRAWDZIWY CZAS do terminala, podobnie jak bez PIPE. –

+2

Jeśli potrzebujesz kodu Python 3; dodaj tag [tag: python-3.x] (widzę python3 w shebangu). Twój napisany kod pozostawi wiszące wątki do czytania. W języku Python 3 '' 'jest literałem w standardzie Unicode, ale' pipe.readline() 'zwraca bajty domyślnie (' '!! B" "' w Pythonie 3). Jeśli to naprawisz, wątek pisarza się nie skończy, ponieważ nic nie umieszcza '' "' w kolejce. – jfs

Odpowiedz

14

Można tarło wątki czytać rur stdout i stderr , napisz do wspólnej kolejki i dołącz do list. Następnie użyj trzeciego wątku, aby wydrukować elementy z kolejki.

import time 
import Queue 
import sys 
import threading 
import subprocess 
PIPE = subprocess.PIPE 


def read_output(pipe, funcs): 
    for line in iter(pipe.readline, ''): 
     for func in funcs: 
      func(line) 
      # time.sleep(1) 
    pipe.close() 

def write_output(get): 
    for line in iter(get, None): 
     sys.stdout.write(line) 

process = subprocess.Popen(
    ['random_print.py'], stdout=PIPE, stderr=PIPE, close_fds=True, bufsize=1) 
q = Queue.Queue() 
out, err = [], [] 
tout = threading.Thread(
    target=read_output, args=(process.stdout, [q.put, out.append])) 
terr = threading.Thread(
    target=read_output, args=(process.stderr, [q.put, err.append])) 
twrite = threading.Thread(target=write_output, args=(q.get,)) 
for t in (tout, terr, twrite): 
    t.daemon = True 
    t.start() 
process.wait() 
for t in (tout, terr): 
    t.join() 
q.put(None) 
print(out) 
print(err) 

Powodem stosowania trzeciej nici - zamiast pozwolić na pierwsze dwie nici zarówno do druku bezpośrednio z zaciskiem - to zapobiec zarówno drukowania sprawozdań z występującym jednocześnie, co może prowadzić w tekście czasami zniekształcone.


Powyższe połączenia random_print.py, która drukuje do stdout i stderr losowo:

import sys 
import time 
import random 

for i in range(50): 
    f = random.choice([sys.stdout,sys.stderr]) 
    f.write(str(i)+'\n') 
    f.flush() 
    time.sleep(0.1) 

Rozwiązanie to pożycza kod i pomysły od J. F. Sebastian, here.


Oto alternatywne rozwiązanie dla systemów uniksowych, używając select.select:

import collections 
import select 
import fcntl 
import os 
import time 
import Queue 
import sys 
import threading 
import subprocess 
PIPE = subprocess.PIPE 

def make_async(fd): 
    # https://stackoverflow.com/a/7730201/190597 
    '''add the O_NONBLOCK flag to a file descriptor''' 
    fcntl.fcntl(
     fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) 

def read_async(fd): 
    # https://stackoverflow.com/a/7730201/190597 
    '''read some data from a file descriptor, ignoring EAGAIN errors''' 
    # time.sleep(1) 
    try: 
     return fd.read() 
    except IOError, e: 
     if e.errno != errno.EAGAIN: 
      raise e 
     else: 
      return '' 

def write_output(fds, outmap): 
    for fd in fds: 
     line = read_async(fd) 
     sys.stdout.write(line) 
     outmap[fd.fileno()].append(line) 

process = subprocess.Popen(
    ['random_print.py'], stdout=PIPE, stderr=PIPE, close_fds=True) 

make_async(process.stdout) 
make_async(process.stderr) 
outmap = collections.defaultdict(list) 
while True: 
    rlist, wlist, xlist = select.select([process.stdout, process.stderr], [], []) 
    write_output(rlist, outmap) 
    if process.poll() is not None: 
     write_output([process.stdout, process.stderr], outmap) 
     break 

fileno = {'stdout': process.stdout.fileno(), 
      'stderr': process.stderr.fileno()} 

print(outmap[fileno['stdout']]) 
print(outmap[fileno['stderr']]) 

Rozwiązanie to wykorzystuje kod i pomysły od Adam Rosenfield's post, here.

+0

możesz dodać 'q.put (None)' after 'process.wait()' i wyjść trzeci wątek na 'Brak' np. ,, dla linii w iter (get, Brak):'. Brak również "pipe.close()". – jfs

+0

@ J.F.Sebastian: Dzięki za poprawki. Przypuśćmy, że 'read_output' z jakiegoś powodu nie nadąża z wyjściem zapisanym w' pipe'. (Próbuję to symulować za pomocą 'czasu.spać (1) "powyżej). Kiedy 'time.sleep (1)' zostaje odkomentowany, 'out' i' err' nie zbierają wszystkich danych wyjściowych zanim zakończy się proces 'process.wait()'. Czy znasz sposób na zagwarantowanie, że 'out' i' err' otrzymają wszystkie dane wyjściowe? – unutbu

+0

't {err, out} .join()' before 'put (None)'. btw, aby uzyskać linie w "czasie rzeczywistym", może pomóc "bufsize = 1" (ignorując 'problem z buforowaniem bloków) – jfs

17

Aby uchwycić i wyświetlanie w tym samym czasie zarówno stdout i stderr od linii technologicznej dziecko po linii w jednym wątku, można użyć asynchronicznego I/O:

#!/usr/bin/env python3 
import asyncio 
import os 
import sys 
from asyncio.subprocess import PIPE 

@asyncio.coroutine 
def read_stream_and_display(stream, display): 
    """Read from stream line by line until EOF, display, and capture the lines. 

    """ 
    output = [] 
    while True: 
     line = yield from stream.readline() 
     if not line: 
      break 
     output.append(line) 
     display(line) # assume it doesn't block 
    return b''.join(output) 

@asyncio.coroutine 
def read_and_display(*cmd): 
    """Capture cmd's stdout, stderr while displaying them as they arrive 
    (line by line). 

    """ 
    # start process 
    process = yield from asyncio.create_subprocess_exec(*cmd, 
      stdout=PIPE, stderr=PIPE) 

    # read child's stdout/stderr concurrently (capture and display) 
    try: 
     stdout, stderr = yield from asyncio.gather(
      read_stream_and_display(process.stdout, sys.stdout.buffer.write), 
      read_stream_and_display(process.stderr, sys.stderr.buffer.write)) 
    except Exception: 
     process.kill() 
     raise 
    finally: 
     # wait for the process to exit 
     rc = yield from process.wait() 
    return rc, stdout, stderr 

# run the event loop 
if os.name == 'nt': 
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows 
    asyncio.set_event_loop(loop) 
else: 
    loop = asyncio.get_event_loop() 
rc, *output = loop.run_until_complete(read_and_display(*cmd)) 
loop.close() 
+0

Ten kod wygląda dobrze. Czy mógłbyś dodać wersję dla Pythona 2.7? – kinORnirvana

+0

@kinORnirvana: 'asyncio' działa tylko na Pythonie 3.3+ Istnieje' trollius' - klon Pythona 2, ale [jest przestarzały] (http://trollius.readthedocs.org/) – jfs

+0

Dobra robota, J.F! Po prostu "pożyczyłem" twój kod dla [tej odpowiedzi] (http://stackoverflow.com/a/41284244/4014959). Jeśli masz jakieś uwagi, sugestie i/lub lepszą odpowiedź, będą one bardzo cenne. –

Powiązane problemy