Streszczenie (lub wersja „tl; dr”): jest łatwe, gdy jest co najwyżej jeden subprocess.PIPE
, w przeciwnym razie jest trudne.
Być może nadszedł czas, aby wyjaśnić trochę, jak to subprocess.Popen
działa.
(Zastrzeżenie: dotyczy Pythona 2.x, chociaż wersja 3.x jest podobna; i jestem dość rozmyślny w wariancie Windows. Rozumiem rzeczy POSIX znacznie lepiej.)
Popen
Funkcja musi radzić sobie z zera do trzech wejść / wyjść, nieco strumieni jednocześnie. Są one oznaczone stdin
, stdout
i stderr
w zwykły sposób.
Możesz podać:
None
, wskazując, że nie chcesz przekierowywać strumienia. Zamiast tego odziedziczy je jak zwykle. Zauważ, że przynajmniej w systemach POSIX nie oznacza to, że użyje Pythona sys.stdout
, tylko rzeczywiste stdout Pythona ; zobacz demo na końcu.
int
Wartość. Jest to „surowy” deskryptor pliku (przynajmniej w POSIX). (Uwaga dodatkowa: PIPE
i STDOUT
faktycznie są int
wewnętrznie, ale są „niemożliwymi” deskryptorami, -1 i -2).
- Strumień - naprawdę każdy obiekt za pomocą
fileno
metody. Popen
znajdzie deskryptor dla tego strumienia, używając stream.fileno()
, a następnie postępuj jak dla int
wartości.
subprocess.PIPE
, wskazując, że Python powinien utworzyć potok.
subprocess.STDOUT
( stderr
tylko dla ): powiedz Pythonowi, aby używał tego samego deskryptora jak dla stdout
. Ma to sens tylko wtedy, gdy podałeś (nie None
) wartość stdout
, a nawet wtedy jest ona potrzebna tylko wtedy, gdy ją ustawisz stdout=subprocess.PIPE
. (W przeciwnym razie możesz podać ten sam argument, który podałeś stdout
, np Popen(..., stdout=stream, stderr=stream)
.).
Najłatwiejsze przypadki (bez rur)
Jeśli nic nie przekierujesz (pozostaw wszystkie trzy jako None
wartość domyślną lub podaj jawnie None
), Pipe
to jest całkiem łatwe. Musi po prostu wyodrębnić podproces i pozwolić mu działać. Lub, jeśli przekierować do nie- PIPE
-an int
lub strumień to fileno()
-To nadal łatwe, jak system operacyjny wykonuje całą pracę. Python musi po prostu wyodrębnić podproces, łącząc swoje stdin, stdout i / lub stderr z dostarczonymi deskryptorami plików.
Wciąż łatwa obudowa: jedna rura
Jeśli przekierujesz tylko jeden strumień, Pipe
nadal będzie to całkiem łatwe. Wybierzmy jeden strumień na raz i obejrzyjmy.
Załóżmy, że chcesz podać jakieś stdin
, ale pozwól stdout
i stderr
nie przekieruj lub przejdź do deskryptora pliku. Jako proces nadrzędny, twój program Python musi po prostu użyć write()
do wysłania danych w dół potoku. Możesz to zrobić samodzielnie, np .:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
lub możesz przekazać dane standardowe, do proc.communicate()
których następnie wykonuje stdin.write
powyższe czynności. Nic nie wraca, więc communicate()
ma tylko jedną prawdziwą robotę: zamyka dla ciebie rurkę. (Jeśli nie zadzwonisz proc.communicate()
, musisz zadzwonić, proc.stdin.close()
aby zamknąć potok, aby podproces wiedział, że nie ma już danych.)
Załóżmy, że chcesz schwytać, stdout
ale odejdź stdin
i zostań stderr
sam. Znowu jest to łatwe: wystarczy zadzwonić proc.stdout.read()
(lub odpowiednik), dopóki nie będzie więcej danych wyjściowych. Ponieważ proc.stdout()
jest to normalny strumień we / wy Pythona, możesz używać na nim wszystkich normalnych konstrukcji, takich jak:
for line in proc.stdout:
lub ponownie możesz użyć proc.communicate()
, co po prostu robi read()
dla ciebie.
Jeśli chcesz tylko przechwytywać stderr
, działa tak samo jak w przypadku stdout
.
Jest jeszcze jedna sztuczka, zanim sprawy staną się trudne. Załóżmy, że chcesz do wychwytywania stdout
, a także uchwycić stderr
ale na tej samej rurze jako standardowe wyjście:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
W tym przypadku subprocess
„kody”! Cóż, musi to zrobić, więc tak naprawdę nie oszukuje: uruchamia podproces zarówno ze standardowym wyjściem standardowym, jak i ze standardowym stderr skierowanym do (pojedynczego) deskryptora potoku, który wraca do procesu nadrzędnego (Python). Po stronie nadrzędnej jest jeszcze tylko jeden deskryptor potoku do odczytu danych wyjściowych. Wszystkie dane wyjściowe „stderr” pojawią się w proc.stdout
, a jeśli zadzwonisz proc.communicate()
, wynikiem stderr (druga wartość w krotce) None
nie będzie ciąg znaków.
Twarde skrzynki: dwie lub więcej rur
Wszystkie problemy pojawiają się, gdy chcesz użyć co najmniej dwóch rur. W rzeczywistości subprocess
sam kod ma ten bit:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
Ale, niestety, tutaj stworzyliśmy co najmniej dwie, a może trzy, różne rury, więc count(None)
zwroty wynoszą 1 lub 0. Musimy robić rzeczy ciężko.
W systemie Windows służy to threading.Thread
do gromadzenia wyników dla self.stdout
i self.stderr
, a wątek nadrzędny dostarcza self.stdin
dane wejściowe (a następnie zamyka potok).
W POSIX, poll
jeśli to możliwe, wykorzystuje to, jeśli to możliwe select
, do akumulowania produkcji i dostarczania wejścia standardowego. Wszystko to działa w (pojedynczym) procesie / wątku nadrzędnym.
Wątki lub ankieta / wybór są tutaj potrzebne, aby uniknąć impasu. Załóżmy na przykład, że przekierowaliśmy wszystkie trzy strumienie do trzech oddzielnych potoków. Załóżmy ponadto, że istnieje niewielki limit ilości danych, które można włożyć do potoku, zanim proces zapisu zostanie zawieszony, czekając, aż proces odczytu „wyczyści” potok z drugiego końca. Ustawmy ten mały limit na jeden bajt, tylko dla ilustracji. (W rzeczywistości tak to działa, z tym wyjątkiem, że limit jest znacznie większy niż jeden bajt.)
Jeśli proces nadrzędny (Python) próbuje zapisać kilka bajtów - powiedzmy, 'go\n'
do proc.stdin
pierwszego bajtu wchodzi, a następnie drugi powoduje zawieszenie procesu Python, czekając, aż podproces odczyta pierwszy bajt, opróżniając potok.
Tymczasem załóżmy, że podproces zdecyduje się wydrukować przyjazne „Cześć! Nie panikuj!” Powitanie. H
Idzie do swojej rury stdout, ale e
powoduje to do zawieszenia, czekając na jego rodzic, aby przeczytać, że H
, opróżnianie rury standardowe wyjście.
Teraz utknęliśmy: proces Python śpi, czekając, aż skończy mówić „idź”, a podproces również śpi, czekając, aż skończy mówiąc „Cześć!
subprocess.Popen
Kod pozwala uniknąć tego problemu z gwintowania-or-select / sondzie. Gdy bajty mogą przejść przez potoki, idą. Gdy nie mogą, tylko wątek (nie cały proces) musi spać - lub, w przypadku wyboru / odpytywania, proces Pythona czeka jednocześnie na „może pisać” lub „dostępne dane”, zapisuje na standardowe wyjście procesu tylko gdy jest miejsce i odczytuje stdout i / lub stderr tylko wtedy, gdy dane są gotowe. proc.communicate()
Kod (właściwie _communicate
gdzie owłosionej przypadki są obsługiwane) powraca po wszystkich danych stdin (jeśli w ogóle) zostały wysłane, a wszystkie dane stdout i / lub stderr zostały zgromadzone.
Jeśli chcesz przeczytać zarówno stdout
i stderr
na dwóch różnych rur (niezależnie od wszelkich stdin
przekierowania), trzeba, aby uniknąć impasu też. Scenariusz impasu jest tutaj inny - występuje, gdy podproces zapisuje coś długo, stderr
podczas gdy pobierasz dane stdout
lub odwrotnie - ale wciąż tam jest.
Demo
Obiecałem zademonstrować, że bez przekierowania Python subprocess
es pisze do podstawowego interfejsu, a nie sys.stdout
. Oto kod:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
Po uruchomieniu:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Zauważ, że pierwsza procedura zakończy się niepowodzeniem, jeśli dodasz stdout=sys.stdout
, ponieważ StringIO
obiekt nie ma fileno
. Drugi pominie hello
jeśli dodasz, stdout=sys.stdout
ponieważ sys.stdout
został przekierowany do os.devnull
.
(Jeśli przekierujesz deskryptor pliku 1 Pythona, podproces będzie podążał za tym przekierowaniem. open(os.devnull, 'w')
Wywołanie tworzy strumień, którego wartość fileno()
jest większa niż 2.)
Popen.poll
jak w poprzednim pytaniu Przepełnienie stosu .