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.Popen
hal 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 Popen
Fungsi perlu berurusan dengan nol-ke-tiga I / O stream, agak secara bersamaan. Ini ditandai stdin
, stdout
dan stderr
seperti 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
int
nilai. Ini adalah deskriptor file "mentah" (setidaknya dalam POSIX). (Catatan: PIPE
dan STDOUT
sebenarnya int
s secara internal, tetapi deskriptor "tidak mungkin", -1 dan -2.)
- Aliran — sungguh, objek apa pun dengan
fileno
metode. Popen
akan menemukan deskriptor untuk aliran itu, menggunakan stream.fileno()
, dan kemudian melanjutkan sebagai int
nilai.
subprocess.PIPE
, menunjukkan bahwa Python harus membuat pipa.
subprocess.STDOUT
( stderr
hanya 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 None
atau pasokan eksplisit None
), Pipe
buatlah itu cukup mudah. Itu hanya perlu spin off proses dan biarkan berjalan. Atau, jika Anda mengarahkan ulang ke non PIPE
-—an int
atau 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, Pipe
masih ada hal-hal yang cukup mudah. Mari kita memilih satu aliran pada satu waktu dan menonton.
Misalkan Anda ingin menyediakan beberapa stdin
, tetapi biarkan stdout
dan stderr
pergi 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.write
ditunjukkan 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 stdout
tetapi pergi stdin
dan stderr
sendirian. 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 stderr
tetapi 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, subprocess
kode 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.Thread
untuk mengumpulkan hasil untuk self.stdout
dan self.stderr
, dan memiliki utas induk mengirimkan self.stdin
data input (dan kemudian menutup pipa).
Pada POSIX, ini digunakan poll
jika 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 H
masuk ke pipa stdout, tetapi yang e
menyebabkan 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.Popen
kode 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 _communicate
di 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 stdout
dan stderr
pada dua pipa yang berbeda (terlepas dari stdin
pengalihan apa pun ), Anda juga harus menghindari jalan buntu. Skenario kebuntuan di sini berbeda — itu terjadi ketika subproses menulis sesuatu yang lama stderr
saat Anda menarik data dari stdout
, atau sebaliknya — tetapi itu masih ada.
Demo
Saya berjanji untuk menunjukkan bahwa, tidak diarahkan, Python subprocess
es 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 StringIO
objek tidak memiliki fileno
. Yang kedua akan menghilangkan hello
jika Anda menambahkan stdout=sys.stdout
sejak sys.stdout
itu 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.poll
seperti pada pertanyaan Stack Overflow sebelumnya .