Ringkasan Eksekutif (atau versi "tl; dr"): mudah saat ada paling banyak subprocess.PIPE, jika tidak sulit.
Mungkin sudah waktunya untuk menjelaskan sedikit tentang bagaimana subprocess.Popenhal itu terjadi.
(Peringatan: ini untuk Python 2.x, meskipun 3.x serupa; dan saya cukup kabur pada varian Windows. Saya mengerti hal-hal POSIX jauh lebih baik.)
The PopenFungsi perlu berurusan dengan nol-ke-tiga I / O stream, agak secara bersamaan. Ini ditandai stdin, stdoutdan stderrseperti biasa.
Anda dapat memberikan:
None, menunjukkan bahwa Anda tidak ingin mengarahkan aliran. Ini akan mewarisi ini seperti biasa. Perhatikan bahwa pada sistem POSIX, setidaknya, ini tidak berarti akan menggunakan Python sys.stdout, hanya stdout Python yang sebenarnya ; lihat demo di akhir.
- Suatu
intnilai. Ini adalah deskriptor file "mentah" (setidaknya dalam POSIX). (Catatan: PIPEdan STDOUTsebenarnya ints secara internal, tetapi deskriptor "tidak mungkin", -1 dan -2.)
- Aliran — sungguh, objek apa pun dengan
filenometode. Popenakan menemukan deskriptor untuk aliran itu, menggunakan stream.fileno(), dan kemudian melanjutkan sebagai intnilai.
subprocess.PIPE, menunjukkan bahwa Python harus membuat pipa.
subprocess.STDOUT( stderrhanya untuk ): beri tahu Python untuk menggunakan deskriptor yang sama seperti untuk stdout. Ini hanya masuk akal jika Anda memberikan nilai (non- None) untuk stdout, dan bahkan kemudian, itu hanya diperlukan jika Anda menetapkan stdout=subprocess.PIPE. (Kalau tidak, Anda bisa memberikan argumen yang sama dengan yang Anda berikan stdout, misalnya Popen(..., stdout=stream, stderr=stream),.)
Kasing termudah (tanpa pipa)
Jika Anda tidak mengarahkan apa pun (biarkan ketiganya sebagai nilai default Noneatau pasokan eksplisit None), Pipebuatlah itu cukup mudah. Itu hanya perlu spin off proses dan biarkan berjalan. Atau, jika Anda mengarahkan ulang ke non PIPE-—an intatau aliran- fileno()itu masih mudah, karena OS melakukan semua pekerjaan. Python hanya perlu untuk memutar subproses, menghubungkan stdin, stdout, dan / atau stderr ke deskriptor file yang disediakan.
Kasing yang masih mudah: satu pipa
Jika Anda mengarahkan hanya satu aliran, Pipemasih ada hal-hal yang cukup mudah. Mari kita memilih satu aliran pada satu waktu dan menonton.
Misalkan Anda ingin menyediakan beberapa stdin, tetapi biarkan stdoutdan stderrpergi tidak diarahkan, atau pergi ke deskriptor file. Sebagai proses induk, program Python Anda hanya perlu digunakan write()untuk mengirim data ke pipa. Anda dapat melakukannya sendiri, misalnya:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
atau Anda dapat meneruskan data stdin ke proc.communicate(), yang kemudian melakukan yang stdin.writeditunjukkan di atas. Tidak ada output yang kembali sehingga communicate()hanya memiliki satu pekerjaan nyata: itu juga menutup pipa untuk Anda. (Jika Anda tidak menelepon proc.communicate()Anda harus menelepon proc.stdin.close()untuk menutup pipa, sehingga subproses tahu tidak ada lagi data yang masuk.)
Misalkan Anda ingin menangkap stdouttetapi pergi stdindan stderrsendirian. Sekali lagi, mudah: cukup panggil proc.stdout.read()(atau setara) sampai tidak ada lagi output. Karena proc.stdout()merupakan aliran I / O Python normal, Anda dapat menggunakan semua konstruksi normal di atasnya, seperti:
for line in proc.stdout:
atau, sekali lagi, Anda dapat menggunakan proc.communicate(), yang cukup read()untuk Anda.
Jika Anda ingin menangkap hanya stderr, itu berfungsi sama dengan stdout.
Ada satu trik lagi sebelum segalanya menjadi sulit. Misalkan Anda ingin menangkap stdout, dan juga menangkap stderrtetapi pada pipa yang sama dengan stdout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Dalam hal ini, subprocess"curang"! Yah, itu harus melakukan ini, jadi itu tidak benar-benar curang: ia memulai subproses dengan stdout dan stderr yang diarahkan ke deskriptor pipa (tunggal) yang mengumpan balik ke proses induknya (Python). Di sisi induk, hanya ada lagi descriptor pipa tunggal untuk membaca output. Semua output "stderr" muncul di proc.stdout, dan jika Anda memanggil proc.communicate(), hasil stderr (nilai kedua dalam tuple) adalah None, bukan string.
Kasing keras: dua atau lebih pipa
Masalahnya semua muncul ketika Anda ingin menggunakan setidaknya dua pipa. Bahkan, subprocesskode itu sendiri memiliki bit ini:
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:
Tapi, sayangnya, di sini kita telah membuat setidaknya dua, dan mungkin tiga, pipa yang berbeda, jadi count(None)pengembaliannya 1 atau 0. Kita harus melakukan hal-hal dengan cara yang sulit.
Pada Windows, ini digunakan threading.Threaduntuk mengumpulkan hasil untuk self.stdoutdan self.stderr, dan memiliki utas induk mengirimkan self.stdindata input (dan kemudian menutup pipa).
Pada POSIX, ini digunakan polljika tersedia, jika tidak select, untuk mengakumulasi output dan mengirimkan input stdin. Semua ini berjalan di (induk) proses / utas.
Utas atau jajak pendapat / pilih diperlukan di sini untuk menghindari kebuntuan. Misalkan, misalnya, bahwa kami telah mengalihkan ketiga aliran ke tiga pipa terpisah. Misalkan lebih lanjut bahwa ada batasan kecil pada seberapa banyak data dapat dimasukkan ke dalam pipa sebelum proses penulisan ditunda, menunggu proses pembacaan untuk "membersihkan" pipa dari ujung yang lain. Mari kita atur batas kecil itu menjadi satu byte, hanya untuk ilustrasi. (Ini sebenarnya cara kerja, kecuali bahwa batasnya jauh lebih besar dari satu byte.)
Jika orang tua (Python) proses mencoba untuk menulis beberapa byte-katakanlah, 'go\n'untuk proc.stdin, byte pertama masuk dan kemudian yang kedua menyebabkan proses Python untuk menangguhkan, menunggu subproses untuk membaca byte pertama, mengosongkan pipa.
Sementara itu, misalkan subproses memutuskan untuk mencetak ramah "Halo! Jangan Panik!" salam. The Hmasuk ke pipa stdout, tetapi yang emenyebabkan untuk menangguhkan, menunggu induknya untuk membaca bahwa H, mengosongkan pipa stdout.
Sekarang kita terjebak: proses Python tertidur, menunggu untuk selesai mengatakan "pergi", dan subproses juga tertidur, menunggu untuk selesai mengatakan "Halo! Jangan Panik!".
The subprocess.Popenkode menghindari masalah ini dengan threading-atau-pilih / jajak pendapat. Ketika byte bisa melewati pipa, mereka pergi. Ketika mereka tidak bisa, hanya utas (bukan keseluruhan proses) yang harus tidur — atau, dalam kasus pilih / jajak pendapat, proses Python menunggu secara bersamaan untuk "dapat menulis" atau "data tersedia", menulis ke stdin proses hanya ketika ada ruang, dan membaca stdout dan / atau stderr hanya ketika data siap. The proc.communicate()kode (sebenarnya _communicatedi mana kasus berbulu ditangani) kembali setelah semua data stdin (jika ada) telah dikirim dan semua data stdout dan / atau stderr telah terakumulasi.
Jika Anda ingin membaca keduanya stdoutdan stderrpada dua pipa yang berbeda (terlepas dari stdinpengalihan apa pun ), Anda juga harus menghindari jalan buntu. Skenario kebuntuan di sini berbeda — itu terjadi ketika subproses menulis sesuatu yang lama stderrsaat Anda menarik data dari stdout, atau sebaliknya — tetapi itu masih ada.
Demo
Saya berjanji untuk menunjukkan bahwa, tidak diarahkan, Python subprocesses menulis ke stdout yang mendasarinya, bukan sys.stdout. Jadi, ini beberapa kode:
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()
Ketika dijalankan:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Perhatikan bahwa rutin pertama akan gagal jika Anda menambahkan stdout=sys.stdout, karena StringIOobjek tidak memiliki fileno. Yang kedua akan menghilangkan hellojika Anda menambahkan stdout=sys.stdoutsejak sys.stdoutitu telah dialihkan ke os.devnull.
(Jika Anda mengarahkan file-deskriptor-1 Python, subproses akan mengikuti pengalihan itu. open(os.devnull, 'w')Panggilan menghasilkan aliran yang fileno()lebih besar dari 2.)
Popen.pollseperti pada pertanyaan Stack Overflow sebelumnya .