Używam skryptu python jako sterownika kodu hydrodynamicznego. Kiedy przychodzi czas na uruchomienie symulacji, używam subprocess.Popen
do uruchomienia kodu, zbieram dane wyjściowe ze stdout i stderr do subprocess.PIPE
--- następnie mogę wydrukować (i zapisać w pliku dziennika) informacje wyjściowe i sprawdzić, czy nie ma błędów . Problem polega na tym, że nie mam pojęcia, jak postępuje kod. Jeśli uruchomię go bezpośrednio z wiersza poleceń, wyświetli mi się informacja o tym, w której iteracji jest, o której godzinie, jaki jest następny krok itd.
Czy istnieje sposób zarówno przechowywać dane wyjściowe (do rejestrowania i sprawdzania błędów), jak i generować dane wyjściowe przesyłane strumieniowo na żywo?
Odpowiednia sekcja mojego kodu:
ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
print "RUN failed\n\n%s\n\n" % (errors)
success = False
if( errors ): log_file.write("\n\n%s\n\n" % errors)
Początkowo byłem potokiem run_command
przez tee
tak, że kopia udał się bezpośrednio do pliku dziennika, a strumień wciąż wyjście bezpośrednio na terminal - ale w ten sposób, że nie można przechowywać żadnych błędów (na mój zasób wiedzy).
Edytować:
Rozwiązanie tymczasowe:
ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
log_file.flush()
następnie w innym terminalu uruchom tail -f log.txt
(st log_file = 'log.txt'
).
źródło
Popen.poll
jak w poprzednim pytaniu Przepełnienie stosu .git
), Robią to tylko wtedy, gdy ich wyjściem jest „urządzenie tty” (testowane przez libcisatty()
). W takim przypadku może być konieczne otwarcie pseudo-tty.flush
, i nie ma potrzeby, aby odczytać z rury stderr jeśli podproces produkuje dużo wyjście stderr. W polu komentarza nie ma wystarczająco dużo miejsca, aby to wyjaśnić ...Odpowiedzi:
Możesz to zrobić na dwa sposoby, tworząc iterator z funkcji
read
lubreadline
i wykonaj następujące czynności:lub
Lub można utworzyć
reader
i dowriter
pliku. Przekazaćwriter
doPopen
i odczytać zreader
W ten sposób dane będą zapisywane
test.log
zarówno na standardowym wyjściu, jak i na nim.Jedyną zaletą podejścia opartego na plikach jest to, że kod nie blokuje się. W międzyczasie możesz robić, co chcesz i czytać, kiedy chcesz,
reader
w sposób nie blokujący. Podczas korzystaniaPIPE
,read
areadline
funkcje będą blokować aż albo jedna postać jest napisane na rurze lub linia jest napisane na rurze odpowiednio.źródło
iter(process.stdout.readline, b'')
(tj Sentinel przeszedł do ITER musi być ciąg binarny, ponieważb'' != ''
.for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding))
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 oznaczonestdin
,stdout
istderr
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 Pythonasys.stdout
, tylko rzeczywiste stdout Pythona ; zobacz demo na końcu.int
Wartość. Jest to „surowy” deskryptor pliku (przynajmniej w POSIX). (Uwaga dodatkowa:PIPE
iSTDOUT
faktycznie sąint
wewnętrznie, ale są „niemożliwymi” deskryptorami, -1 i -2).fileno
metody.Popen
znajdzie deskryptor dla tego strumienia, używającstream.fileno()
, a następnie postępuj jak dlaint
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 dlastdout
. Ma to sens tylko wtedy, gdy podałeś (nieNone
) wartośćstdout
, a nawet wtedy jest ona potrzebna tylko wtedy, gdy ją ustawiszstdout=subprocess.PIPE
. (W przeciwnym razie możesz podać ten sam argument, który podałeśstdout
, npPopen(..., stdout=stream, stderr=stream)
.).Najłatwiejsze przypadki (bez rur)
Jeśli nic nie przekierujesz (pozostaw wszystkie trzy jako
None
wartość domyślną lub podaj jawnieNone
),Pipe
to jest całkiem łatwe. Musi po prostu wyodrębnić podproces i pozwolić mu działać. Lub, jeśli przekierować do nie-PIPE
-anint
lub strumień tofileno()
-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ólstdout
istderr
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 .:lub możesz przekazać dane standardowe, do
proc.communicate()
których następnie wykonujestdin.write
powyższe czynności. Nic nie wraca, więccommunicate()
ma tylko jedną prawdziwą robotę: zamyka dla ciebie rurkę. (Jeśli nie zadzwoniszproc.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:lub ponownie możesz użyć
proc.communicate()
, co po prostu robiread()
dla ciebie.Jeśli chcesz tylko przechwytywać
stderr
, działa tak samo jak w przypadkustdout
.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: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ę wproc.stdout
, a jeśli zadzwoniszproc.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: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 dlaself.stdout
iself.stderr
, a wątek nadrzędny dostarczaself.stdin
dane wejściowe (a następnie zamyka potok).W POSIX,
poll
jeśli to możliwe, wykorzystuje to, jeśli to możliweselect
, 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'
doproc.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, alee
powoduje to do zawieszenia, czekając na jego rodzic, aby przeczytać, żeH
, 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
istderr
na dwóch różnych rur (niezależnie od wszelkichstdin
przekierowania), trzeba, aby uniknąć impasu też. Scenariusz impasu jest tutaj inny - występuje, gdy podproces zapisuje coś długo,stderr
podczas gdy pobierasz danestdout
lub odwrotnie - ale wciąż tam jest.Demo
Obiecałem zademonstrować, że bez przekierowania Python
subprocess
es pisze do podstawowego interfejsu, a niesys.stdout
. Oto kod:Po uruchomieniu:
Zauważ, że pierwsza procedura zakończy się niepowodzeniem, jeśli dodasz
stdout=sys.stdout
, ponieważStringIO
obiekt nie mafileno
. Drugi pominiehello
jeśli dodasz,stdout=sys.stdout
ponieważsys.stdout
został przekierowany doos.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.)źródło
sys.stdout
) przechodzi do standardowego interfejsu Pythona , a nie standardowego programu Pythona (sys.
). Przyznaję, że to ... dziwne rozróżnienie. Czy istnieje lepszy sposób na sformułowanie tego?asyncio
kod, który implementuje „twardą część” (obsługuje jednocześnie wiele potoków) w przenośny sposób . Można to porównać do kodu używającego wielu wątków (teed_call()
), aby zrobić to samo .Możemy również użyć domyślnego iteratora plików do odczytu standardowego wyjścia zamiast używania iteracyjnej konstrukcji z readline ().
źródło
Jeśli możesz korzystać z bibliotek stron trzecich, możesz użyć czegoś takiego
sarge
(ujawnienie: jestem jego opiekunem). Ta biblioteka umożliwia nieblokujący dostęp do strumieni wyjściowych z podprocesów - jest warstwowa nadsubprocess
modułem.źródło
Rozwiązanie 1: Zaloguj się
stdout
ORAZstderr
jednocześnie w czasie rzeczywistymProste rozwiązanie, które rejestruje jednocześnie stdout AND stderr, linia po linii w czasie rzeczywistym do pliku dziennika.
Rozwiązanie 2: Funkcja
read_popen_pipes()
umożliwiająca iterację po obu rurach (stdout / stderr), jednocześnie w czasie rzeczywistymźródło
Dobrym, ale „ciężkim” rozwiązaniem jest użycie Twisted - patrz na dole.
Jeśli chcesz żyć tylko ze standardowym wyjściem, coś w tym stylu powinno działać:
(Jeśli użyjesz read (), to spróbuje odczytać cały „plik”, co nie jest przydatne, tak naprawdę moglibyśmy użyć tutaj czegoś, co odczytuje wszystkie dane, które są teraz w potoku)
Można również spróbować podejść do tego za pomocą wątków, np .:
Teraz możemy potencjalnie dodać również stderr, mając dwa wątki.
Zauważ jednak, że dokumenty podprocesów odradzają bezpośrednie korzystanie z tych plików i zaleca się ich używać
communicate()
(głównie dotyczy zakleszczeń, które moim zdaniem nie są problemem powyżej), a rozwiązania są trochę nieporadne, więc wygląda na to, że moduł podprocesów nie jest całkiem gotowy zadanie (patrz także: http://www.python.org/dev/peps/pep-3145/ ) i musimy spojrzeć na coś innego.Bardziej zaangażowanym rozwiązaniem jest użycie Twisted, jak pokazano tutaj: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html
Sposób, w jaki to robisz za pomocą Twisted, polega na utworzeniu procesu za pomocą
reactor.spawnprocess()
i zapewnieniu,ProcessProtocol
że następnie przetwarza dane wyjściowe asynchronicznie. Przykładowy kod Pythona Twisted znajduje się tutaj: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.pyźródło
read()
wywołanie, dopóki podproces nie zakończy się i proces nadrzędny nie odbierzeEOF
w potoku.Oprócz wszystkich tych odpowiedzi jedno proste podejście może wyglądać następująco:
Zapętlaj czytelny strumień, dopóki jest on czytelny, a jeśli otrzyma pusty wynik, zatrzymaj go.
Kluczem tutaj jest to, że
readline()
zwraca linię (z\n
na końcu), o ile istnieje wyjście i jest puste, jeśli naprawdę jest na końcu.Mam nadzieję, że to komuś pomoże.
źródło
Na podstawie wszystkich powyższych proponuję nieco zmodyfikowaną wersję (python3):
None
Kod:
źródło
returncode
część była w moim przypadku kluczowa.Wygląda na to, że wyjście buforowane wierszem będzie dla ciebie działać, w takim przypadku może pasować coś takiego jak poniżej. (Zastrzeżenie: nie zostało przetestowane). To da stdout podprocesu tylko w czasie rzeczywistym. Jeśli chcesz mieć stderr i stdout w czasie rzeczywistym, musisz zrobić coś bardziej złożonego
select
.źródło
Dlaczego nie ustawić
stdout
bezpośrednio nasys.stdout
? A jeśli musisz również wyprowadzać dane do dziennika, możesz po prostu przesłonić metodę zapisu f.źródło
stdout
deskryptor pliku na deskryptor pliku przekazywanego obiektu pliku. Metoda write nigdy nie zostałaby wywołana (przynajmniej tak działa podproces dla stderr, wydaje mi się, że jest taki sam dla stdout).Wszystkie powyższe rozwiązania, których próbowałem, nie były w stanie oddzielić wyjścia stderr i stdout (wiele potoków) lub zostały zablokowane na zawsze, gdy bufor potoku systemu operacyjnego był pełny, co dzieje się, gdy polecenie, które uruchamiasz, uruchamia się zbyt szybko (pojawia się ostrzeżenie w pythonie poll () instrukcja podprocesu). Jedyny niezawodny sposób, który znalazłem, to wybór, ale jest to rozwiązanie tylko dla posix:
źródło
Podobne do poprzednich odpowiedzi, ale następujące rozwiązanie działało dla mnie w systemie Windows przy użyciu Python3, aby zapewnić wspólną metodę drukowania i logowania w czasie rzeczywistym ( pobieranie-realtime-output-using-python ):
źródło
Myślę, że
subprocess.communicate
metoda jest nieco myląca: w rzeczywistości wypełnia stdout i stderr , które określasz wsubprocess.Popen
.Jednak czytanie z
subprocess.PIPE
których można dostarczać dosubprocess.Popen
„s stdout i stderr parametrów ostatecznie zapełni się bufory rur OS i impasu swoją aplikację (zwłaszcza jeśli masz wiele procesów / wątków, które należy wykorzystaćsubprocess
).Moim proponowanym rozwiązaniem jest dostarczenie stdout i stderr plików - i odczytanie zawartości plików zamiast czytania z impasu
PIPE
. Pliki te mogą byćtempfile.NamedTemporaryFile()
- do których można również uzyskać dostęp w celu odczytu podczas ich zapisywania przezsubprocess.communicate
.Poniżej znajduje się przykładowe użycie:
A to jest kod źródłowy, który jest gotowy do użycia z tyloma komentarzami, ile mógłbym podać, aby wyjaśnić, co robi:
Jeśli używasz Pythona 2, najpierw zainstaluj najnowszą wersję pakietu subprocess32 z pypi.
źródło
Oto klasa, której używam w jednym z moich projektów. Przekierowuje wyjście podprocesu do dziennika. Na początku próbowałem po prostu nadpisać metodę write, ale to nie działa, ponieważ podproces nigdy jej nie wywoła (przekierowanie odbywa się na poziomie skryptu plików). Używam więc własnego potoku, podobnie jak w module podprocesu. Ma to tę zaletę, że hermetyzuje całą logikę rejestrowania / drukowania w adapterze i można po prostu przekazać instancje rejestratora do
Popen
:subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))
Jeśli nie potrzebujesz rejestrować, ale po prostu chcesz z niego skorzystać
print()
, możesz oczywiście usunąć duże części kodu i skrócić klasę. Można również rozszerzyć ją__enter__
oraz__exit__
metody i nazywająfinished
się__exit__
tak, że można łatwo używać go jako kontekst.źródło
Żadne z rozwiązań Pythonic nie działało dla mnie. Okazało się, że coś
proc.stdout.read()
podobnego może blokować na zawsze.Dlatego używam w
tee
ten sposób:To rozwiązanie jest wygodne, jeśli już używasz
shell=True
.${PIPESTATUS}
przechwytuje status powodzenia całego łańcucha poleceń (dostępne tylko w Bash). Gdybym pominął&& exit ${PIPESTATUS}
, to zawsze zwróciłoby zero, ponieważtee
nigdy nie zawodzi.unbuffer
może być konieczne do wydrukowania każdej linii natychmiast do terminala, zamiast czekać zbyt długo, aż „bufor bufora” zostanie wypełniony. Jednak unbuffer połyka status wyjścia aser (SIG Abort) ...2>&1
loguje również stderror do pliku.źródło