Odczytaj dane wejściowe przesyłania strumieniowego z subprocess.communicate ()

84

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

Jak mogę wydrukować każdą linię tego procesu stdoutw sposób strumieniowy, aby zobaczyć wynik w postaci, w jakiej jest generowany, ale nadal blokować zakończenie procesu przed kontynuowaniem?

subprocess.communicate() wydaje się dawać wszystkie wyniki naraz.

Heinrich Schmetterling
źródło

Odpowiedzi:

44

Uwaga, myślę, że metoda JF 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 lszakończy się zbyt szybko, pętla while może zakończyć się przed odczytaniem wszystkich danych.

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

output = proc.communicate()[0]
print output,
unutbu
źródło
1
czy ten schemat padł ofiarą problemu z blokowaniem bufora, do którego odnosi się dokument w języku Python?
Heinrich Schmetterling
@Heinrich, problem z blokowaniem bufora nie jest czymś, co dobrze rozumiem. Uważam (tylko po googlowaniu), że ten problem występuje tylko wtedy, gdy nie czytasz ze standardowego wyjścia (i stderr?) Wewnątrz pętli while. Więc myślę, że powyższy kod jest w porządku, ale nie mogę powiedzieć na pewno.
unutbu
1
To faktycznie cierpi z powodu problemu z blokowaniem, kilka lat temu nie miałem końca z kłopotami, w których readline blokowałoby się, dopóki nie otrzyma nowej linii, nawet jeśli proces się skończył. Nie pamiętam rozwiązania, ale myślę, że miało to coś wspólnego z wykonywaniem odczytów w wątku roboczym i po prostu zapętlaniem while proc.poll() is None: time.sleep(0)lub czymś w tym rodzaju. Zasadniczo - musisz albo upewnić się, że wyjściowy znak nowej linii jest ostatnią rzeczą, którą wykonuje proces (ponieważ nie możesz dać interpreterowi czasu na ponowne zapętlenie), albo musisz zrobić coś „wymyślnego”.
dash-tom-bang
@Heinrich: Alex Martelli pisze o tym, jak uniknąć impasu tutaj: stackoverflow.com/questions/1445627/…
unutbu
6
Blokowanie bufora jest prostsze, niż się czasem wydaje: bloki nadrzędne oczekujące na wyjście dziecka + bloki podrzędne czekające na odczytanie przez rodzica i zwolnienie miejsca w potoku komunikacyjnym, który jest pełny = zakleszczenie. To takie proste. Im mniejsza rura, tym większe prawdopodobieństwo.
MarcH
160

Aby uzyskać dane wyjściowe podprocesu wiersz po wierszu, gdy tylko podproces opróżni swój standardowy bufor:

#!/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()służy do odczytywania wierszy, gdy tylko zostaną napisane, w celu obejścia błędu odczytu z wyprzedzeniem w Pythonie 2 .

Jeśli standardowe wyjście podprocesu używa buforowania blokowego zamiast buforowania linii w trybie nieinteraktywnym (co prowadzi do opóźnienia w wyjściu do momentu pełnego lub opróżnienia bufora dziecka), można spróbować wymusić niebuforowane wyjście za pomocą pexpect, ptymoduły lub unbuffer, stdbuf, scriptnarzędzia , patrz P: Dlaczego nie wystarczy użyć rury (popen ())?


Oto kod w Pythonie 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 generuje bajty podprocesów bez zmian; Python 3 używa trybu tekstowego (wyjście cmd jest dekodowane przy użyciu locale.getpreferredencoding(False)kodowania).

jfs
źródło
co oznacza b ''?
Aaron,
4
b''jest bytesliterałem w Pythonie 2.7 i Pythonie 3.
jfs
2
@JinghaoShi: bufsize=1może mieć znaczenie, jeśli również piszesz (używając p.stdin) do podprocesu, np. Może to pomóc w uniknięciu impasu podczas wykonywania interaktywnej ( pexpectpodobnej) wymiany - zakładając, że nie ma problemów z buforowaniem w samym procesie potomnym. Jeśli tylko czytasz, to, jak powiedziałem, różnica dotyczy tylko wydajności: jeśli tak nie jest, czy możesz podać minimalny kompletny przykład kodu, który to pokazuje?
jfs
1
@ealeon: tak. Wymaga technik, które mogą odczytywać stdout / stderr jednocześnie, chyba że połączysz stderr ze stdout (przechodząc stderr=subprocess.STDOUTdo Popen()). Zobacz także, wątki lub rozwiązania asyncio, które są tam połączone.
jfs
2
@saulspatz jeśli stdout=PIPEnie przechwytuje danych wyjściowych (nadal widzisz je na ekranie), Twój program może zamiast tego drukować na stderr lub bezpośrednio na terminal. Aby połączyć stdout i stderr, przejdź stderr=subprocess.STDOUT(zobacz mój poprzedni komentarz). Aby przechwycić dane wyjściowe wydrukowane bezpośrednio na tty, możesz użyć rozwiązań pexpect, pty. . Oto bardziej złożony przykład kodu .
jfs
6

Uważam, że najprostszym sposobem gromadzenia danych wyjściowych z procesu w sposób strumieniowy jest następujący:

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ć pusty ciąg tylko w EOF, po zakończeniu procesu - w przeciwnym razie będzie blokować, jeśli nie ma nic do odczytania ( readline()zawiera znak nowej linii, więc w pustych wierszach zwraca „\ n”). Pozwala to uniknąć niezręcznego końcowego communicate()wywołania po pętli.

W przypadku plików z bardzo długimi wierszami read()może być preferowane zmniejszenie maksymalnego wykorzystania pamięci - przekazywana liczba jest dowolna, ale wykluczenie jej powoduje odczytanie całego potoku na raz, co prawdopodobnie nie jest pożądane.

D Coetzee
źródło
4
data = proc.stdout.read()bloków do momentu odczytania wszystkich danych. Możesz to pomylić z os.read(fd, maxsize)tym, że możesz wrócić wcześniej (gdy tylko będą dostępne dane).
jfs
Masz rację, myliłem się. Jeśli jednak jako argument zostanie przekazana rozsądna liczba bajtów, read()to działa dobrze i podobnie readline()działa dobrze, o ile maksymalna długość linii jest rozsądna. Odpowiednio zaktualizowałem moją odpowiedź.
D Coetzee
3

Jeśli chcesz mieć podejście nieblokujące, nie używaj process.communicate(). Jeśli ustawisz subprocess.Popen()argument stdoutna PIPE, możesz czytać process.stdouti sprawdzać, czy proces nadal działa przy użyciu process.poll().

Lukáš Lalinský
źródło
3

Jeśli po prostu próbujesz przekazać dane wyjściowe w czasie rzeczywistym, trudno jest uzyskać prostsze 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 dokumentację dla subprocess.check_call () .

Jeśli chcesz przetworzyć dane wyjściowe, z pewnością zapętl je. Ale jeśli nie, po prostu nie komplikuj.

Edycja: JF Sebastian zwraca uwagę zarówno na to, że wartości domyślne parametrów stdout i stderr są przekazywane do sys.stdout i sys.stderr, jak i że zakończy się niepowodzeniem, jeśli sys.stdout i sys.stderr zostaną zastąpione (powiedzmy, do przechwytywania danych wyjściowych w testy).

Nate
źródło
Nie zadziała, jeśli sys.stdoutlub sys.stderrzostaną zastąpione obiektami podobnymi do pliku, które nie mają prawdziwego fileno (). Jeżeli sys.stdout, sys.stderrnie zastępuje to jest jeszcze prostsza: subprocess.check_call(args).
jfs
Dzięki! Zdałem sobie sprawę z kaprysów zastąpienia sys.stdout / stderr, ale jakoś nigdy nie zdawałem sobie sprawy, że jeśli pominiesz argumenty, przekazuje stdout i stderr we właściwe miejsca. Podoba mi call()się, check_call()chyba że chcę CalledProcessError.
Nate
python -mthis: „Błędy nigdy nie powinny przechodzić po cichu. Chyba że zostały wyraźnie wyciszone”. dlatego przykładowy kod powinien preferować check_call()nad call().
jfs
Heh. Wiele programów, które kończę, call()zwraca niezerowe kody błędów w warunkach braku błędów, ponieważ są one straszne. Zatem w naszym przypadku niezerowy kod błędu nie jest w rzeczywistości błędem.
Nate
tak. Istnieją programy, grepktóre mogą zwracać niezerowy kod zakończenia, nawet jeśli nie ma błędu - są wyjątkami. Domyślnie zerowy status wyjścia wskazuje na sukces.
jfs
1
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'))
Petr J
źródło
1
zawsze dobrze jest wyjaśnić, co robi twoje rozwiązanie, aby ludzie lepiej zrozumieli
DaFois
2
Powinieneś rozważyć użycie shlex.split(myCommand)zamiast myCommand.split(). Uwzględnia również spacje w cytowanych argumentach.
UtahJarhead
0

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

  1. Umożliwia przechwycenie kodu zakończenia procesu powłoki (nie udało mi się uzyskać kodu zakończenia podczas korzystania z withkonstrukcji)
  2. Wyprowadza również 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)
bigfoot56
źródło