Chcę subprocess.Popen()
rsync.exe w systemie Windows i wydrukować standardowe wyjście w Pythonie.
Mój kod działa, ale nie rejestruje postępu, dopóki nie zakończy się przesyłanie pliku! Chcę wydrukować postęp dla każdego pliku w czasie rzeczywistym.
Używając Pythona 3.1 teraz, ponieważ słyszałem, powinno być lepsze 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()
python
subprocess
stdout
John A.
źródło
źródło
Odpowiedzi:
Kilka praktycznych zasad
subprocess
.shell=True
. Niepotrzebnie wywołuje dodatkowy proces powłoki, aby wywołać program.sys.argv
w Pythonie jest lista, a więc jestargv
w C. Więc przekazać listę doPopen
, aby zadzwonić do podprocesów, a nie ciąg.stderr
do a,PIPE
gdy go nie czytasz.stdin
kiedy do niego nie piszesz.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())
To powiedziawszy, jest prawdopodobne, że rsync buforuje swoje dane wyjściowe, gdy wykryje, że jest podłączony do potoku zamiast terminala. Jest to zachowanie domyślne - po podłączeniu do potoku programy muszą jawnie opróżniać standardowe wyjście, aby uzyskać wyniki w czasie rzeczywistym, w przeciwnym razie standardowa biblioteka C będzie buforować.
Aby to sprawdzić, spróbuj zamiast tego uruchomić:
cmd = [sys.executable, 'test_out.py']
i utwórz
test_out.py
plik z zawartością:import sys import time print ("Hello") sys.stdout.flush() time.sleep(10) print ("World")
Wykonanie tego podprocesu powinno dać ci „Hello” i odczekać 10 sekund przed podaniem „World”. Jeśli tak się stanie z powyższym kodem Pythona, a nie z
rsync
, oznacza to, żersync
sam buforuje dane wyjściowe, więc nie masz szczęścia.Rozwiązaniem byłoby podłączenie się bezpośrednio do
pty
, używając czegoś takiego jakpexpect
.źródło
shell=False
jest słuszna, gdy konstruujesz wiersz poleceń, zwłaszcza z danych wprowadzonych przez użytkownika. Niemniej jednakshell=True
jest również przydatny, gdy otrzymujesz całą linię poleceń z zaufanego źródła (np. Zakodowaną na stałe w skrypcie).shell=True
. Pomyśl o tym - wywołujesz inny proces w swoim systemie operacyjnym, obejmujący alokację pamięci, użycie dysku, planowanie procesora, tylko po to, aby podzielić ciąg ! I jeden, do którego dołączyłeś !! Możesz podzielić w Pythonie, ale i tak łatwiej jest pisać każdy parametr osobno. Ponadto, na podstawie listy oznacza, że nie ma ucieczki znaki specjalne powłoki: przestrzenie,;
,>
,<
,&
.. Twoje parametry mogą zawierać te znaki i nie trzeba się martwić! Naprawdę nie widzę powodu, aby go używaćshell=True
, chyba że używasz polecenia tylko dla powłoki.csv
modułu. Ale na przykład twój potok w Pythonie wyglądałby tak:p = Popen(['cut', '-f1'], stdin=open('longfile.tab'), stdout=PIPE) ; p2 = Popen(['head', '-100'], stdin=p.stdout, stdout=PIPE) ; result, stderr = p2.communicate() ; print result
Zauważ, że możesz pracować z długimi nazwami plików i znakami specjalnymi powłoki bez konieczności ucieczki, teraz, gdy powłoka nie jest zaangażowana. Jest też dużo szybszy, ponieważ jest o jeden proces mniej.for line in iter(p.stdout.readline, b'')
zamiastfor line in p.stdout
w Pythonie 2, w przeciwnym razie wiersze nie są odczytywane w czasie rzeczywistym, nawet jeśli proces źródłowy nie buforuje swoich danych wyjściowych.Wiem, że to stary temat, ale teraz jest rozwiązanie. 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())
źródło
preexec_fn=os.setpgrp
aby program przetrwał jego skrypt nadrzędny 2. pominiesz czytanie z potoku procesu 3. proces wyprowadza dużo danych, wypełniając potok 4. utkniesz na wiele godzin , próbując dowiedzieć się, dlaczego uruchomiony program zamyka się po jakimś losowym czasie . Odpowiedź z @nosklo bardzo mi pomogła.W Linuksie miałem ten sam problem z pozbyciem się buforowania. W końcu użyłem "stdbuf -o0" (lub unbuffer z oczekiwania), aby pozbyć się buforowania PIPE.
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE) stdout = proc.stdout
Mógłbym wtedy użyć select.select na stdout.
Zobacz też /unix/25372/
źródło
stdbuf -o0
okazał się bardzo przydatny z kilkoma testami pytest / pytest-bdd Napisałem, że spawnujemy aplikację w C ++ i sprawdzam, czy emituje określone instrukcje dziennika. Bezstdbuf -o0
tych testów te testy potrzebowały 7 sekund, aby uzyskać (buforowane) dane wyjściowe z programu C ++. Teraz działają niemal natychmiast!pytest
, nie mogłem uzyskać jej wyniku.stdbuf
czy to.W zależności od przypadku użycia możesz również chcieć wyłączyć buforowanie w samym podprocesie.
Jeśli podproces będzie procesem w Pythonie, możesz to zrobić przed wywołaniem:
os.environ["PYTHONUNBUFFERED"] = "1"
Lub alternatywnie przekaż to w
env
argumencie doPopen
.W przeciwnym razie, jeśli korzystasz z systemu Linux / Unix, możesz użyć
stdbuf
narzędzia. Np. Jak:cmd = ["stdbuf", "-oL"] + cmd
Zobacz również tutaj o
stdbuf
lub inne opcje.źródło
for line in p.stdout: ...
zawsze blokuje do następnego wysunięcia wiersza.
Aby zachować zachowanie w czasie rzeczywistym, musisz 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
Pętla while jest pozostawiana, gdy proces potomny zamyka swoje standardowe wyjście lub kończy pracę.
read()/read(-1)
będzie blokować, dopóki proces potomny nie zamknie swojego wyjścia standardowego lub nie zakończy działania.źródło
inchar
nigdy nie jestNone
używanyif not inchar:
(read()
zwraca pusty ciąg w EOF). btw, Gorzejfor line in p.stdout
, nie drukuje nawet pełnych wierszy w czasie rzeczywistym w Pythonie 2 (for line in
zamiast tego można użyć iter (p.stdout.readline, '') ``).for line in p.stdout:
działa na Pythonie 3. Upewnij się, że rozumiesz różnicę między''
(ciąg znaków Unicode) ab''
(bajty). Zobacz Python: read streaming input from subprocess.communicate ()Twój problem to:
for line in p.stdout: print(">>> " + str(line.rstrip())) p.stdout.flush()
sam iterator ma dodatkowe buforowanie.
Spróbuj zrobić tak:
while True: line = p.stdout.readline() if not line: break print line
źródło
Nie możesz zmusić stdout do drukowania niebuforowanego do potoku (chyba że możesz przepisać program, który drukuje na stdout), więc oto moje rozwiązanie:
Przekieruj standardowe wyjście do sterr, które nie jest buforowane.
'<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żesz odróżnić od stdout lub stderr, ale wszystkie dane wyjściowe otrzymasz natychmiast.
Mam nadzieję, że pomoże to każdemu rozwiązującemu ten problem.
źródło
1>&2
zmienia tylko pliki, na które wskazują deskryptory plików, przed uruchomieniem programu. Sam program nie jest w stanie rozróżnić przekierowania stdout do stderr (1>&2
) lub odwrotnie (2>&1
), więc nie będzie to miało wpływu na zachowanie programu podczas buforowania. Tak czy inaczej,1>&2
składnia jest interpretowana przez powłokę.subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
nie powiedzie się, ponieważ nie określonoshell=True
.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)
źródło
Aby uniknąć buforowania wyjścia, możesz 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ść stare, wciąż dostarcza rozwiązania, które działało dla mnie.
PPS : otrzymałem tę odpowiedź z innego pytania
źródło
p = subprocess.Popen(command, bufsize=0, universal_newlines=True)
Piszę GUI dla rsync w Pythonie i mam te same problemy. Ten problem niepokoi mnie od kilku dni, dopóki nie znajdę go w pyDoc.
Wygląda na to, że rsync wyświetli '\ r', gdy trwa translacja.
źródło
Zauważyłem, że nie ma wzmianki o używaniu pliku tymczasowego jako pośredniego. Poniższe informacje omijają problemy z buforowaniem, przesyłając dane do pliku tymczasowego i umożliwiają analizowanie danych pochodzących z rsync bez łączenia się z pty. Przetestowałem następujące elementy na Linuksie, a dane wyjściowe rsync różnią się na różnych platformach, więc wyrażenia regularne do analizowania danych wyjściowych mogą się różnić:
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...
źródło
while not p.poll()
prowadzi do nieskończonej pętli, jeśli podproces zakończy się pomyślnie z 0, użyjp.poll() is None
zamiast tegoopen(file_name)
może się nie powieśćcommand_argv = ["stdbuf","-i0","-o0","-e0"] + command_argv
i dzwonię:popen = subprocess.Popen(cmd, stdout=subprocess.PIPE)
i teraz mogę czytać bez buforowaniajeśli uruchomisz coś takiego w wątku i zapiszesz właściwość ffmpeg_time we właściwości metody, aby uzyskać do niej dostęp, działałoby bardzo dobrze, otrzymuję takie wyniki: wyjście będzie takie, jak gdybyś używał wątków w tkinter
input = 'path/input_file.mp4' output = 'path/input_file.mp4' command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\"" process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True) for line in self.process.stdout: reg = re.search('\d\d:\d\d:\d\d', line) ffmpeg_time = reg.group(0) if reg else '' print(ffmpeg_time)
źródło
W Pythonie 3 jest rozwiązanie, które usuwa polecenie z wiersza poleceń i dostarcza ładnie dekodowane ciągi w czasie rzeczywistym, gdy są odbierane.
Odbiornik (
receiver.py
):import subprocess import sys cmd = sys.argv[1:] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) for line in p.stdout: print("received: {}".format(line.rstrip().decode("utf-8")))
Przykładowy prosty program, który może generować dane wyjściowe w czasie rzeczywistym (
dummy_out.py
):import time import sys for i in range(5): print("hello {}".format(i)) sys.stdout.flush() time.sleep(1)
Wynik:
$python receiver.py python dummy_out.py received: hello 0 received: hello 1 received: hello 2 received: hello 3 received: hello 4
źródło