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 stdout
w 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.
python
subprocess
Heinrich Schmetterling
źródło
źródło
Odpowiedzi:
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
ls
zakoń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,
źródło
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”.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
,pty
moduły lubunbuffer
,stdbuf
,script
narzę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).źródło
b''
jestbytes
literałem w Pythonie 2.7 i Pythonie 3.bufsize=1
może mieć znaczenie, jeśli również piszesz (używającp.stdin
) do podprocesu, np. Może to pomóc w uniknięciu impasu podczas wykonywania interaktywnej (pexpect
podobnej) 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?stderr=subprocess.STDOUT
doPopen()
). Zobacz także, wątki lub rozwiązania asyncio, które są tam połączone.stdout=PIPE
nie 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 .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()
lubread()
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ńcowegocommunicate()
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.źródło
data = proc.stdout.read()
bloków do momentu odczytania wszystkich danych. Możesz to pomylić zos.read(fd, maxsize)
tym, że możesz wrócić wcześniej (gdy tylko będą dostępne dane).read()
to działa dobrze i podobniereadline()
działa dobrze, o ile maksymalna długość linii jest rozsądna. Odpowiednio zaktualizowałem moją odpowiedź.Jeśli chcesz mieć podejście nieblokujące, nie używaj
process.communicate()
. Jeśli ustawiszsubprocess.Popen()
argumentstdout
naPIPE
, możesz czytaćprocess.stdout
i sprawdzać, czy proces nadal działa przy użyciuprocess.poll()
.źródło
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).
źródło
sys.stdout
lubsys.stderr
zostaną zastąpione obiektami podobnymi do pliku, które nie mają prawdziwego fileno (). Jeżelisys.stdout
,sys.stderr
nie zastępuje to jest jeszcze prostsza:subprocess.check_call(args)
.call()
się,check_call()
chyba że chcęCalledProcessError
.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()
nadcall()
.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.grep
któ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.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'))
źródło
shlex.split(myCommand)
zamiastmyCommand.split()
. Uwzględnia również spacje w cytowanych argumentach.Dodanie kolejnego rozwiązania Python3 z kilkoma małymi zmianami:
with
konstrukcji)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)
źródło