2009-10-22 24 views
58

Chcę subprocess.Popen() rsync.exe w systemie Windows i wydrukować standardowe wyjście w Pythonie.przechwytywanie stdout w czasie rzeczywistym z podprocesu

Mój kod działa, ale nie przechwytuje postępu do momentu zakończenia przesyłania pliku! Chcę wydrukować postęp każdego pliku w czasie rzeczywistym.

Korzystanie z Python 3.1 teraz, ponieważ słyszałem, że powinno być lepiej w obsłudze IO.

import subprocess, time, os, sys 

cmd = "rsync.exe -vaz -P source/ dest/" 
p, line = True, 'start' 


p = subprocess.Popen(cmd, 
        shell=True, 
        bufsize=64, 
        stdin=subprocess.PIPE, 
        stderr=subprocess.PIPE, 
        stdout=subprocess.PIPE) 

for line in p.stdout: 
    print(">>> " + str(line.rstrip())) 
    p.stdout.flush() 
+2

Duplikat: http://stackoverflow.com/questions/1085071/real-time-intercepting-of-stdout-from-another-process-in-python, http://stackoverflow.com/questions/ 874815/how-do-i-get-real-time-information-back-from-a-subprocess-popen-in-python-2-5, http://stackoverflow.com/questions/527197/intercepting-stdout- of-a-subprocess-while-it-is-is-running –

+1

(Z Google?) wszystkie PIPE zakleszczą się, gdy jeden z buforów PIPE zostanie wypełniony i nie zostanie odczytany. na przykład stdout, kiedy stderr jest wypełniony. Nigdy nie przepuszczaj PIPE, którego nie zamierzasz czytać. –

+0

Czy ktoś mógłby wyjaśnić, dlaczego nie można po prostu ustawić stdout na sys.stdout zamiast subprocess.PIPE? – Mike

Odpowiedz

2

Zmień standardowe wyjście z procesu rsync na niebuforowane.

p = subprocess.Popen(cmd, 
        shell=True, 
        bufsize=0, # 0=unbuffered, 1=line-buffered, else buffer-size 
        stdin=subprocess.PIPE, 
        stderr=subprocess.PIPE, 
        stdout=subprocess.PIPE) 
+3

Buforowanie odbywa się po stronie rsync, zmiana atrybutu bufsize na stronie Pythona nie pomoże. – nosklo

+12

Dla każdego, kto szuka, odpowiedź nosklo jest całkowicie błędna: wyświetlanie postępu rsync nie jest buforowane; prawdziwym problemem jest to, że podprocesor zwraca obiekt pliku, a interfejs iteratora pliku ma słabo udokumentowany wewnętrzny bufor nawet z bufsize = 0, co wymaga wielokrotnego wywoływania readline(), jeśli potrzebujesz wyników zanim bufor się zapełni. –

71

Niektóre zasady dla subprocess.

  • Nigdy użycie shell=True. Niepotrzebnie wywołuje dodatkowy proces powłoki, aby wywołać twój program.
  • Podczas wywoływania procesów argumenty są przekazywane jako listy. sys.argv w pythonie to lista, podobnie jak argv w C. Tak więc przekazujesz listę do Popen, aby wywołać podprocesy, a nie ciąg.
  • Nie przekierowuj stderr do PIPE, gdy jej nie czytasz.
  • Nie przekierowuj stdin, gdy nie piszesz do niego.

przykład:

import subprocess, time, os, sys 
cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"] 

p = subprocess.Popen(cmd, 
        stdout=subprocess.PIPE, 
        stderr=subprocess.STDOUT) 

for line in iter(p.stdout.readline, b''): 
    print(">>> " + line.rstrip()) 

Niemniej, jest prawdopodobne, że bufory tworzenia serwerów swoją moc, gdy wykryje, że jest podłączony do rury, a nie gniazda. Jest to zachowanie domyślne - po podłączeniu do potoku, programy muszą jawnie przepuszczać standardowe wyjście dla wyników w czasie rzeczywistym, w przeciwnym razie standardowa biblioteka C będzie buforować.

Aby przetestować za to, spróbuj uruchomić ten zamiast:

cmd = [sys.executable, 'test_out.py'] 

i utworzyć plik test_out.py z treścią:

import sys 
import time 
print ("Hello") 
sys.stdout.flush() 
time.sleep(10) 
print ("World") 

wykonujących to podproces powinno dać "Hello" i odczekaj 10 sekund przed podaniem "Świata". Jeśli dzieje się to z powyższym kodem Pythona, a nie z rsync, oznacza to, że samo rsync buforuje wynik, więc nie masz szczęścia.

Rozwiązaniem byłoby połączenie bezpośrednio z pty, używając czegoś takiego jak pexpect.

+6

'shell = False' jest właściwym rozwiązaniem podczas tworzenia linii poleceń, szczególnie z danych wprowadzonych przez użytkownika. Ale mimo to 'shell = True' jest przydatny, gdy dostajesz całą linię poleceń z zaufanego źródła (np. Na stałe w skrypcie). –

+9

@Disis Otkidach: Nie sądzę, że uzasadnia to użycie 'shell = True'. Pomyśl o tym - uruchamiasz inny proces w systemie operacyjnym, obejmujący przydzielanie pamięci, wykorzystanie dysku, planowanie procesora, po prostu ** dzielenie ciągu **! I dołączyłeś się sam! Można podzielić na pytona, ale i tak łatwiej jest pisać każdy parametr osobno. Używanie listy oznacza również, że nie musisz uciekać od specjalnych znaków powłoki: spacji, ';', '>', '<', '&' .. Twoje parametry mogą zawierać te znaki i nie musisz się martwić ! Nie widzę powodu, aby naprawdę używać 'shell = True', chyba że używasz polecenia powłoki. – nosklo

+0

nosklo, powinno wynosić: p = subprocess.Popen (CMD, standardowe wyjście = subprocess.PIPE, stderr = subprocess.STDOUT) –

0

Zauważyłem, że nie ma żadnej wzmianki o używaniu pliku tymczasowego jako pośredniego. Poniższe problemy z ładowaniem buforują wyjście do pliku tymczasowego i pozwala na parsowanie danych pochodzących z rsync bez łączenia się z pty.I badane następujące w polu linux, a wyjście z rsync zwykle różnią się między platformami, więc wyrażenia regularne do analizowania danych wyjściowych może być różna:

import subprocess, time, tempfile, re 

pipe_output, file_name = tempfile.TemporaryFile() 
cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"] 

p = subprocess.Popen(cmd, stdout=pipe_output, 
        stderr=subprocess.STDOUT) 
while p.poll() is None: 
    # p.poll() returns None while the program is still running 
    # sleep for 1 second 
    time.sleep(1) 
    last_line = open(file_name).readlines() 
    # it's possible that it hasn't output yet, so continue 
    if len(last_line) == 0: continue 
    last_line = last_line[-1] 
    # Matching to "[bytes downloaded] number% [speed] number:number:number" 
    match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line) 
    if not match_it: continue 
    # in this case, the percentage is stored in match_it.group(1), 
    # time in match_it.group(2). We could do something with it here... 
+0

to nie jest w czasie rzeczywistym. Plik nie rozwiązuje problemu z buforowaniem po stronie rsync. – jfs

+0

tempfile.TemporaryFile może sam się usunąć, aby ułatwić czyszczenie w przypadku wyjątków – jfs

+0

nie przekierowuj stderr = STDOUT to są błędy ukrywania w tym przypadku – jfs

7

Nie można dostać stdout wydrukować niebuforowana do rury (chyba możesz przepisać program, który drukuje na standardowe wyjście), więc oto moje rozwiązanie:

Przekieruj stdout na sterr, który nie jest buforowany. '<cmd> 1>&2' powinien to zrobić. Otwórz proces w następujący sposób: myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
Nie można odróżnić od standardowe wyjście lub stderr, ale natychmiast uzyskać wszystkie dane wyjściowe.

Mam nadzieję, że pomoże to każdemu rozwiązać ten problem.

+3

Czy próbowałeś? Ponieważ nie działa. Jeśli stdout jest buforowany w tym procesie, nie zostanie przekierowany do stderr w taki sam sposób, w jaki nie jest przekierowywany do PIPE lub pliku. –

+3

To jest błędne. standardowe buforowanie odbywa się w samym programie. Składnia powłoki '1> i 2' po prostu zmienia które pliki wskazuje deskryptor plików przed uruchomieniem programu. Sam program nie może odróżnić przekierowania stdout na stderr ('1> i 2') lub vice versa (' 2> & 1'), więc nie będzie to miało wpływu na zachowanie buforowe programu. I tak czy inaczej '1 Składnia> & 2' jest interpretowana przez powłokę. 'subprocess.Popen (' 1> & 2', stderr = subprocess.PIPE)' zawiedzie, ponieważ nie podano 'shell = True'. –

+0

W przypadku, gdy ludzie będą czytać to: Próbowałem używać stderr zamiast stdout, pokazuje dokładnie to samo zachowanie. – martinthenext

7
for line in p.stdout: 
    ... 

zawsze blokuje do następnego podania do wiersza.

Dla „real-time” zachowanie trzeba zrobić coś takiego:

while True: 
    inchar = p.stdout.read(1) 
    if inchar: #neither empty string nor None 
    print(str(inchar), end='') #or end=None to flush immediately 
    else: 
    print('') #flush for implicit line-buffering 
    break 

while pętli pozostaje, gdy proces potomny zamyka stdout lub wychodzi. read()/read(-1) będzie blokować, dopóki proces potomny nie zamknie standardowego wyjścia lub nie zakończy działania.

+1

'inchar' nigdy nie jest' Brak' użyj 'if not inchar:' zamiast ('read()' zwraca pusty ciąg na EOF). btw, Gorsze jest 'dla linii w p.stdout' nie wypisuje nawet pełnych linii w czasie rzeczywistym w Pythonie 2 (' dla linii w 'iter (p.stdout.readline, '') może być użyty zamiast tego). – jfs

+1

Testowałem to z Pythona 3.4 na osx, i to nie działa. – qed

+1

@qed: 'dla linii w p.stdout:' działa na Pythonie 3. Pamiętaj, aby zrozumieć różnicę między '' '(ciągiem Unicode) i' b''' (bajtami). Zobacz [Python: odczyt strumienia wejściowego z subprocess.communicate()] (http://stackoverflow.com/a/17698359/4279) – jfs

5

Twój problem jest:

for line in p.stdout: 
    print(">>> " + str(line.rstrip())) 
    p.stdout.flush() 

sama iterator ma dodatkowego buforowania.

spróbować zrobić tak:

while True: 
    line = p.stdout.readline() 
    if not line: 
    break 
    print line 
24

Wiem, że to stary wątek, ale nie jest to rozwiązanie teraz. Wywołaj rsync z opcją --outbuf = L. Przykład:

cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest'] 
p = subprocess.Popen(cmd, 
        stdout=subprocess.PIPE) 
for line in iter(p.stdout.readline, b''): 
    print '>>> {}'.format(line.rstrip()) 
+1

To działa i należy go przegłosować, aby oszczędzić przyszłym czytelnikom przewijania przez wszystkie powyższe okno dialogowe. – VectorVictor

+0

@VectorVictor Nie wyjaśnia, co się dzieje i dlaczego to się dzieje. Może być tak, że program działa, dopóki: 1. dodasz 'preexec_fn = os.setpgrp' aby program przeżyć swój skrypt nadrzędnej 2. Państwo ominąć dalsze czytanie z rury procesu wywołującego tę 3. wyjścia procesowe mnóstwo danych, napełniając rurę. 4. utknąłeś przez wiele godzin, próbując dowiedzieć się, dlaczego uruchomiony program kończy się * po pewnym losowym czasie *. Odpowiedź od @nosklo bardzo mi pomogła. – danuker

1

Aby uniknąć buforowanie wyjścia może chcesz spróbować pexpect,

child = pexpect.spawn(launchcmd,args,timeout=None) 
while True: 
    try: 
     child.expect('\n') 
     print(child.before) 
    except pexpect.EOF: 
     break 

PS: Wiem, że to pytanie jest dość stary, nadal dostarczając rozwiązania, które pracowały dla mnie.

PPS: dostał taką odpowiedź od innego pytania

8

W systemie Linux, miałem ten sam problem pozbycia się buforowania. W końcu użyłem "stdbuf -o0" (lub, unbuffer from expect), aby pozbyć się buforowania PIPE.

proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE) 
stdout = proc.stdout 

Mogłem wtedy użyć select.select na standardowe wyjście.

Zobacz także https://unix.stackexchange.com/questions/25372/

+1

Dla każdego, kto próbuje pobrać kod C-Stdout z Pythona, mogę potwierdzić, że to rozwiązanie było dla mnie jedynym rozwiązaniem. Aby było jasne, mówię o dodaniu "stdbuf", "-o0" do mojej istniejącej listy poleceń w Popen. – Reckless

2
p = subprocess.Popen(command, 
           bufsize=0, 
           universal_newlines=True) 

Piszę GUI dla rsync w python i mają te same probelms. Ten problem niepokoi mnie przez kilka dni, dopóki nie znajdę tego w pyDoc.

If universal_newlines is True, the file objects stdout and stderr are opened as text files in universal newlines mode. Lines may be terminated by any of '\n', the Unix end-of-line convention, '\r', the old Macintosh convention or '\r\n', the Windows convention. All of these external representations are seen as '\n' by the Python program.

Wygląda na to, że rsync wyświetli "\ r" podczas tłumaczenia.

0

użytkowania | tee przekierować stdout do pliku o nazwie out.txt podczas wyświetlania stdout w czasie rzeczywistym na zacisku

import subprocess, time, os, sys 

cmd = "rsync.exe -vaz -P source/ dest/ | tee out.txt" 

p, line = True, 'start' 

p = subprocess.Popen(cmd, 
       shell=True) 

p.wait() 

można uzyskać stdout z out.txt plików po podproces.

# Get stdout from file out.txt 
f = open('out.txt') 
out = f.read() 
f.close() 
Powiązane problemy