Saya ingin subprocess.Popen()
rsync.exe di Windows, dan mencetak stdout dengan Python.
Kode saya berfungsi, tetapi tidak menangkap kemajuan sampai transfer file selesai! Saya ingin mencetak kemajuan untuk setiap file secara real time.
Menggunakan Python 3.1 sekarang karena saya mendengarnya seharusnya lebih baik dalam menangani 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
sumber
sumber
Jawaban:
Beberapa aturan praktis untuk
subprocess
.shell=True
. Itu tidak perlu memanggil proses shell ekstra untuk memanggil program Anda.sys.argv
di python adalah daftar, dan begitu jugaargv
di C. Jadi Anda lulus daftar untukPopen
memanggil subproses, bukan string.stderr
kePIPE
saat Anda tidak membacanya.stdin
ketika Anda tidak sedang menulis untuk itu.Contoh:
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())
Meskipun demikian, kemungkinan besar rsync menyangga keluarannya ketika ia mendeteksi bahwa ia terhubung ke pipa dan bukan ke terminal. Ini adalah perilaku default - ketika terhubung ke pipa, program harus secara eksplisit membersihkan stdout untuk hasil realtime, jika tidak, library C standar akan buffer.
Untuk mengujinya, coba jalankan ini sebagai gantinya:
cmd = [sys.executable, 'test_out.py']
dan buat
test_out.py
file dengan isinya:import sys import time print ("Hello") sys.stdout.flush() time.sleep(10) print ("World")
Menjalankan subproses itu akan memberi Anda "Halo" dan menunggu 10 detik sebelum memberikan "Dunia". Jika itu terjadi dengan kode python di atas dan bukan dengan
rsync
, itu berartirsync
output buffering, jadi Anda kurang beruntung.Solusinya adalah menghubungkan langsung ke
pty
, menggunakan sesuatu sepertipexpect
.sumber
shell=False
adalah hal yang benar ketika Anda membuat baris perintah terutama dari data yang dimasukkan pengguna. Tapi bagaimanapunshell=True
juga berguna ketika Anda mendapatkan seluruh baris perintah dari sumber terpercaya (misalnya hardcode di skrip).shell=True
. Pikirkan tentang itu - Anda menjalankan proses lain di OS Anda, yang melibatkan alokasi memori, penggunaan disk, penjadwalan prosesor, hanya untuk membagi string ! Dan yang Anda gabungkan sendiri !! Anda dapat membaginya dengan python, tetapi lebih mudah menulis setiap parameter secara terpisah. Juga, menggunakan daftar sarana Anda tidak harus melarikan diri karakter shell khusus: ruang,;
,>
,<
,&
.. parameter Anda dapat berisi orang-orang chars dan Anda tidak perlu khawatir! Saya tidak bisa melihat alasan untuk menggunakanshell=True
, sungguh, kecuali Anda menjalankan perintah khusus shell.csv
modul. Tetapi sebagai contoh, pipeline Anda dengan python adalah: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
Perhatikan bahwa Anda dapat bekerja dengan nama file yang panjang dan karakter khusus shell tanpa harus keluar, karena sekarang shell tidak terlibat. Juga jauh lebih cepat karena ada satu proses yang lebih sedikit.for line in iter(p.stdout.readline, b'')
alih-alihfor line in p.stdout
di Python 2 jika tidak, garis tidak dibaca secara real time meskipun proses sumber tidak menyangga keluarannya.Saya tahu ini adalah topik lama, tetapi sekarang ada solusinya. Panggil rsync dengan opsi --outbuf = L. Contoh:
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())
sumber
preexec_fn=os.setpgrp
agar program bertahan dari skrip induknya 2. Anda melewatkan membaca dari pipa proses 3. proses mengeluarkan banyak data, mengisi pipa 4. Anda terjebak selama berjam-jam , mencoba mencari tahu mengapa program yang Anda jalankan berhenti setelah beberapa waktu . Jawaban dari @nosklo sangat membantu saya.Di Linux, saya memiliki masalah yang sama dalam menghilangkan buffering. Saya akhirnya menggunakan "stdbuf -o0" (atau, unbuffer from expect) untuk menghilangkan buffering PIPE.
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE) stdout = proc.stdout
Saya kemudian dapat menggunakan select.select di stdout.
Lihat juga /unix/25372/
sumber
stdbuf -o0
terbukti sangat berguna dengan sekumpulan tes pytest / pytest-bdd Saya menulis yang menelurkan aplikasi C ++ dan memverifikasi bahwa itu memancarkan pernyataan log tertentu. Tanpanyastdbuf -o0
, pengujian ini membutuhkan 7 detik untuk mendapatkan keluaran (buffer) dari program C ++. Sekarang mereka berjalan hampir seketika!pytest
, tidak mungkin bagi saya untuk mendapatkan keluarannya.stdbuf
melakukannya.Bergantung pada kasus penggunaan, Anda mungkin juga ingin menonaktifkan buffering di subproses itu sendiri.
Jika subproses adalah proses Python, Anda dapat melakukan ini sebelum panggilan:
os.environ["PYTHONUNBUFFERED"] = "1"
Atau sebagai alternatif, berikan ini dalam
env
argumen kePopen
.Jika tidak, jika Anda menggunakan Linux / Unix, Anda dapat menggunakan
stdbuf
alat tersebut. Misalnya seperti:cmd = ["stdbuf", "-oL"] + cmd
Lihat juga di sini tentang
stdbuf
atau opsi lain.sumber
for line in p.stdout: ...
selalu memblokir sampai feed baris berikutnya.
Untuk perilaku "waktu nyata", Anda harus melakukan sesuatu seperti ini:
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-loop ditinggalkan saat proses anak menutup stdout atau keluarnya.
read()/read(-1)
akan memblokir sampai proses anak menutup stdout atau keluarnya.sumber
inchar
tidak pernahNone
digunakanif not inchar:
sebagai gantinya (read()
mengembalikan string kosong pada EOF). btw, Lebih buruk lagifor line in p.stdout
tidak mencetak bahkan baris penuh secara realtime dengan Python 2 (for line in
iter (p.stdout.readline, '') `bisa digunakan sebagai gantinya).for line in p.stdout:
bekerja pada Python 3. Pastikan untuk memahami perbedaan antara''
(Unicode string) danb''
(bytes). Lihat Python: membaca input streaming dari subprocess.communicate ()Masalah Anda adalah:
for line in p.stdout: print(">>> " + str(line.rstrip())) p.stdout.flush()
iterator itu sendiri memiliki buffering ekstra.
Coba lakukan seperti ini:
while True: line = p.stdout.readline() if not line: break print line
sumber
Anda tidak bisa mendapatkan stdout untuk mencetak unbuffered ke pipa (kecuali jika Anda dapat menulis ulang program yang mencetak ke stdout), jadi inilah solusi saya:
Alihkan stdout ke sterr, yang tidak di-buffer.
'<cmd> 1>&2'
harus melakukannya. Buka prosesnya sebagai berikut:myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
Anda tidak dapat membedakan dari stdout atau stderr, tetapi Anda langsung mendapatkan semua output.
Semoga ini bisa membantu siapa pun yang menangani masalah ini.
sumber
1>&2
hanya mengubah file mana yang ditunjuk oleh deskriptor file sebelum meluncurkan program. Program itu sendiri tidak dapat membedakan antara mengalihkan stdout ke stderr (1>&2
) atau sebaliknya (2>&1
) jadi ini tidak akan berpengaruh pada perilaku buffering program. Dan cara apa pun1>&2
sintaksnya diinterpretasikan oleh shell.subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
akan gagal karena Anda belum menentukanshell=True
.Ubah stdout dari proses rsync menjadi unbuffered.
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)
sumber
Untuk menghindari caching output Anda mungkin ingin mencoba pexpect,
child = pexpect.spawn(launchcmd,args,timeout=None) while True: try: child.expect('\n') print(child.before) except pexpect.EOF: break
PS : Saya tahu pertanyaan ini cukup lama, masih memberikan solusi yang berhasil untuk saya.
PPS : dapatkan jawaban ini dari pertanyaan lain
sumber
p = subprocess.Popen(command, bufsize=0, universal_newlines=True)
Saya menulis GUI untuk rsync dengan python, dan memiliki masalah yang sama. Masalah ini mengganggu saya selama beberapa hari hingga saya menemukan ini di pyDoc.
Tampaknya rsync akan mengeluarkan '\ r' saat terjemahan sedang berlangsung.
sumber
Saya perhatikan bahwa tidak disebutkan menggunakan file sementara sebagai perantara. Berikut ini adalah mengatasi masalah buffering dengan mengeluarkan ke file sementara dan memungkinkan Anda untuk mengurai data yang berasal dari rsync tanpa menghubungkan ke pty. Saya menguji yang berikut ini di kotak linux, dan keluaran rsync cenderung berbeda di seluruh platform, sehingga ekspresi reguler untuk mengurai keluaran dapat bervariasi:
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...
sumber
while not p.poll()
mengarah ke loop tak terbatas jika subproses berhasil keluar dengan 0, gunakanp.poll() is None
sebagai gantinyaopen(file_name)
mungkin gagalcommand_argv = ["stdbuf","-i0","-o0","-e0"] + command_argv
dan panggil:popen = subprocess.Popen(cmd, stdout=subprocess.PIPE)
dan sekarang saya dapat membaca tanpa bufferingjika Anda menjalankan sesuatu seperti ini di utas dan menyimpan properti ffmpeg_time dalam properti metode sehingga Anda dapat mengaksesnya, itu akan bekerja dengan sangat baik Saya mendapatkan output seperti ini: output seperti jika Anda menggunakan threading di 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)
sumber
Di Python 3, inilah solusinya, yang mengambil perintah dari baris perintah dan memberikan string yang didekodekan secara real-time dengan baik saat diterima.
Penerima (
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")))
Contoh program sederhana yang dapat menghasilkan keluaran waktu nyata (
dummy_out.py
):import time import sys for i in range(5): print("hello {}".format(i)) sys.stdout.flush() time.sleep(1)
Keluaran:
$python receiver.py python dummy_out.py received: hello 0 received: hello 1 received: hello 2 received: hello 3 received: hello 4
sumber