Odczyt strumieniowego wejścia z podprocesu.komunikacja()

Używam Pythona subprocess.communicate() do odczytu stdout z procesu, który działa przez około minutę.

Jak mogę wydrukować każdą linię tego procesu stdout w sposób strumieniowy, aby zobaczyć wyjście tak, jak zostało wygenerowane, ale nadal zablokować zakończenie procesu przed kontynuacją?

subprocess.communicate() wydaje się, że daje wszystkie wyniki na raz.

Author: martineau, 2010-04-26

7 answers

Proszę zauważyć, myślę, że metoda J. F. Sebastiana (poniżej) jest lepsza.


Oto prosty przykład (bez sprawdzania błędów):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

Jeśli ls kończy się zbyt szybko, wtedy pętla while może zakończyć się zanim przeczytasz wszystkie dane.

Możesz złapać resztę w stdout w ten sposób:

output = proc.communicate()[0]
print output,
 47
Author: unutbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:09

Aby uzyskać wyjście podprocesu linia po linii, gdy tylko podproces opróżni swój bufor stdout:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter() jest używany do odczytu wierszy zaraz po ich zapisaniu do workaround błąd "read-ahead" w Pythonie 2 .

Jeśli podprocess ' stdout używa buforowania bloków zamiast buforowania linii w trybie nieinteraktywnym (co prowadzi do opóźnienia wyjścia, dopóki bufor potomka nie jest pełny lub spłukany jawnie przez potomka), możesz spróbować wymusić niebuforowane wyjście za pomocą pexpect, pty moduły lub unbuffer, stdbuf, script Narzędzia , zobacz P: Dlaczego nie użyć po prostu rury (popen ())?


Oto Kod Pythona 3:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

Uwaga: W przeciwieństwie do Pythona 2, który wyświetla bajty podprocesu tak, jak jest; Python 3 używa trybu tekstowego (wyjście cmd jest dekodowane za pomocą kodowania locale.getpreferredencoding(False)).

 163
Author: jfs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-18 12:38:19

Uważam, że najprostszy sposób zbierania danych wyjściowych z procesu w sposób strumieniowy jest taki:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

Funkcja readline() lub read() powinna zwracać tylko pusty łańcuch na EOF, po zakończeniu procesu - w przeciwnym razie zablokuje się, jeśli nie ma nic do odczytu (readline() zawiera nowy wiersz, więc na pustych liniach zwraca "\N"). Pozwala to uniknąć niewygodnego wywołania końcowego communicate() po pętli.

Na plikach z bardzo długimi wierszami read() można zmniejszyć maksymalnie wykorzystanie pamięci - liczba przekazywana do niej jest dowolna, ale jej wyłączenie powoduje odczyt całego wyjścia rury naraz, co prawdopodobnie nie jest pożądane.

 6
Author: D Coetzee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-22 23:45:27

Jeśli chcesz nieblokującego podejścia, nie używaj process.communicate(). Jeśli ustawisz argument subprocess.Popen() stdout na PIPE, Możesz odczytać z process.stdout i sprawdzić, czy proces nadal działa za pomocą process.poll().

 3
Author: Lukáš Lalinský,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-26 18:29:30

Jeśli po prostu próbujesz przekazać dane wyjściowe w czasie rzeczywistym, trudno jest uzyskać prostsze rozwiązanie niż to:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

Zobacz docs dla podprocesu.check_call () .

Jeśli chcesz przetworzyć wyjście, oczywiście, zapętl je. Ale jeśli tego nie zrobisz, po prostu to uprość.

Edytuj: J. F. Sebastian zwraca uwagę, że wartości domyślne dla parametrów stdout i stderr przechodzą do sys.stdout i sys.stderr, i że to się nie powiedzie, jeśli sys.stdout i sys.stderr zostały zastąpione (powiedzmy, do przechwytywania danych wyjściowych w testach).

 2
Author: Nate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:20
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))
 1
Author: Petr J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-12 23:22:11

Dodanie kolejnego rozwiązania python3 z kilkoma małymi zmianami:

  1. pozwala na przechwycenie kodu zakończenia procesu powłoki (nie udało mi się uzyskać kodu zakończenia podczas korzystania z with} construct)
  2. również wyprowadza stderr w czasie rzeczywistym
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)
 0
Author: bigfoot56,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-15 23:05:42