Domanda

Sto provando a scrivere uno script wrapper per un programma a riga di comando (verifica svnadmin) che mostrerà un buon indicatore di avanzamento per l'operazione. Ciò richiede che io sia in grado di vedere ogni riga di output dal programma spostato non appena viene emesso.

Ho pensato che avrei semplicemente eseguito il programma usando subprocess.Popen, avrei usato stdout=PIPE, quindi avrei letto ogni riga appena arrivata e avrei agito di conseguenza. Tuttavia, quando ho eseguito il seguente codice, l'output sembrava essere bufferizzato da qualche parte, facendolo apparire in due blocchi, le righe da 1 a 332, quindi da 333 a 439 (l'ultima riga di output)

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, shell = True)
for line in p.stdout:
    print line.replace('\n', '')

Dopo aver esaminato un po 'la documentazione sui sottoprocessi, ho scoperto il parametro bufsize su Popen, quindi ho provato a impostare bufsize su 1 (buffer per riga) e 0 (nessun buffer), ma nessuno dei due valori sembrava cambiare il modo in cui venivano consegnate le linee.

A questo punto stavo cominciando a cercare paglia, quindi ho scritto il seguente loop di output:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

ma ha ottenuto lo stesso risultato.

È possibile ottenere l'output del programma 'in tempo reale' di un programma eseguito usando il sottoprocesso? C'è qualche altra opzione in Python che è compatibile con il futuro (non exec*)?

È stato utile?

Soluzione

Ho provato questo, e per qualche ragione mentre il codice

for line in p.stdout:
  ...

respinge in modo aggressivo, la variante

while True:
  line = p.stdout.readline()
  if not line: break
  ...

no. Apparentemente si tratta di un bug noto: http://bugs.python.org/issue3907 (Il problema è ora " Chiuso " al 29 agosto 2018)

Altri suggerimenti

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()

Puoi provare questo:

import subprocess
import sys

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

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()

Se si utilizza readline anziché read, in alcuni casi il messaggio di input non viene stampato. Provalo con un comando che richiede un input inline e vedi di persona.

È possibile indirizzare direttamente l'output del sottoprocesso agli stream. Esempio semplificato:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

Mi sono imbattuto nello stesso problema qualche tempo fa. La mia soluzione era quella di abbandonare l'iterazione per il metodo read, che tornerà immediatamente anche se il tuo sottoprocesso non è stato completato, ecc.

Problema di output in tempo reale risolto: Ho riscontrato un problema simile in Python, mentre catturavo l'output in tempo reale dal programma c. Ho aggiunto & Quot; fflush (stdout) ; & Quot; nel mio codice C. Ha funzionato per me. Ecco il codice snip

lt &; Lt &; C Programma & Gt; & Gt;

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

lt &; Lt &; Programma Python & Gt; & Gt;

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)

lt &; Lt &; USCITA gt &; Gt &; Stampa: conteggio 1 Stampa: conteggio 2 Stampa: Count 3

Spero che sia d'aiuto.

~ Sairam

È possibile utilizzare un iteratore su ogni byte nell'output del sottoprocesso. Ciò consente l'aggiornamento in linea (le righe che terminano con '\ r' sovrascrivono la riga di output precedente) dal sottoprocesso:

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")

A seconda del caso d'uso, potresti anche voler disabilitare il buffering nel sottoprocesso stesso.

Se il sottoprocesso sarà un processo Python, è possibile farlo prima della chiamata:

os.environ["PYTHONUNBUFFERED"] = "1"

O in alternativa passalo nell'argomento env a Popen.

Altrimenti, se sei su Linux / Unix, puoi usare lo strumento stdbuf. Per esempio. come:

cmd = ["stdbuf", "-oL"] + cmd

Vedi anche qui su <=> o altre opzioni.

(Vedi anche qui per la stessa risposta.)

Il Streaming sottoprocessa stdin e stdout con asyncio in Python post sul blog di Kevin McCarthy mostra come fare con asyncio:

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Utilizzo di pexpect [ http://www.noah.org/wiki/Pexpect ] con readline non bloccanti risolverà questo problema. Deriva dal fatto che le pipe sono bufferizzate e quindi l'output della tua app viene bufferizzato dalla pipe, quindi non puoi arrivare a quell'output fino a quando il buffer non si riempie o il processo termina.

Ho usato questa soluzione per ottenere output in tempo reale su un sottoprocesso. Questo ciclo si interromperà non appena il processo verrà completato, lasciando fuori la necessità di un'istruzione break o di un possibile ciclo infinito.

sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()

Trovato questo " plug-and-play " funzione qui . Ha funzionato come un fascino!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)

Soluzione completa:

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()

Questo è lo scheletro di base che uso sempre per questo. Semplifica l'implementazione dei timeout ed è in grado di gestire inevitabili processi di sospensione.

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()

(Questa soluzione è stata testata con Python 2.7.15)
Hai solo bisogno di sys.stdout.flush () dopo ogni riga read / write:

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top