Tóm tắt điều hành (hoặc phiên bản "tl; dr"): thật dễ dàng khi có nhiều nhất subprocess.PIPE
, nếu không thì khó.
Có lẽ đã đến lúc giải thích một chút về việc làm thế nào subprocess.Popen
.
(Hãy cẩn thận: đây là cho Python 2.x, mặc dù 3.x tương tự nhau và tôi khá mờ về biến thể Windows. Tôi hiểu rõ hơn về công cụ POSIX.)
Các Popen
chức năng cần phải đối phó với zero-to-ba I / O suối, hơi cùng một lúc. Đây là những ký hiệu stdin
, stdout
và stderr
như thường lệ.
Bạn có thể cung cấp:
None
, chỉ ra rằng bạn không muốn chuyển hướng luồng. Nó sẽ kế thừa những thứ này như bình thường thay thế. Lưu ý rằng trên các hệ thống POSIX, ít nhất, điều này không có nghĩa là nó sẽ sử dụng thiết bị xuất chuẩn của Python sys.stdout
, chỉ là thiết bị xuất chuẩn thực tế của Python ; xem bản demo ở cuối
- Một
int
giá trị. Đây là một mô tả tệp "thô" (ít nhất là trong POSIX). (Lưu ý bên: PIPE
và STDOUT
thực sự int
là nội bộ, nhưng là mô tả "không thể", -1 và -2.)
- Một luồng Stream thực sự, bất kỳ đối tượng với một
fileno
phương thức. Popen
sẽ tìm mô tả cho luồng đó, sử dụng stream.fileno()
và sau đó tiến hành như một int
giá trị.
subprocess.PIPE
, chỉ ra rằng Python sẽ tạo ra một đường ống.
subprocess.STDOUT
( stderr
chỉ dành cho ): bảo Python sử dụng cùng một mô tả như cho stdout
. Điều này chỉ có ý nghĩa nếu bạn cung cấp một None
giá trị (không ) cho stdout
, và thậm chí sau đó, nó chỉ cần thiết nếu bạn đặt stdout=subprocess.PIPE
. (Nếu không, bạn chỉ có thể cung cấp cùng một đối số bạn đã cung cấp stdout
, ví dụ : Popen(..., stdout=stream, stderr=stream)
.)
Các trường hợp dễ nhất (không có đường ống)
Nếu bạn chuyển hướng không có gì (để cả ba làm None
giá trị mặc định hoặc cung cấp rõ ràng None
), Pipe
thì điều đó khá dễ dàng. Nó chỉ cần quay vòng quá trình con và để nó chạy. Hoặc, nếu bạn chuyển hướng đến một phi PIPE
-an int
hoặc một dòng suối là fileno()
-nó vẫn dễ dàng, như hệ điều hành làm tất cả công việc. Python chỉ cần loại bỏ tiến trình con, kết nối stdin, stdout và / hoặc stderr của nó với các mô tả tệp được cung cấp.
Trường hợp vẫn dễ dàng: một ống
Nếu bạn chỉ chuyển hướng một luồng, Pipe
vẫn có những thứ khá dễ dàng. Hãy chọn một luồng tại một thời điểm và xem.
Giả sử bạn muốn cung cấp một số stdin
, nhưng hãy để stdout
và stderr
không chuyển hướng, hoặc đi đến một mô tả tập tin. Là tiến trình cha, chương trình Python của bạn chỉ cần sử dụng write()
để gửi dữ liệu xuống đường ống. Bạn có thể tự làm điều này, vd:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
hoặc bạn có thể truyền dữ liệu stdin tới proc.communicate()
, sau đó thực stdin.write
hiện như trên. Không có đầu ra nào quay trở lại nên communicate()
chỉ có một công việc thực sự khác: nó cũng đóng đường ống cho bạn. (Nếu bạn không gọi, proc.communicate()
bạn phải gọi proc.stdin.close()
để đóng đường ống, để quy trình con biết rằng không có thêm dữ liệu nào đi qua.)
Giả sử bạn muốn chụp stdout
nhưng để lại stdin
và stderr
một mình. Một lần nữa, thật dễ dàng: chỉ cần gọi proc.stdout.read()
(hoặc tương đương) cho đến khi không còn đầu ra nữa. Vì proc.stdout()
là luồng I / O Python bình thường, bạn có thể sử dụng tất cả các cấu trúc bình thường trên nó, như:
for line in proc.stdout:
hoặc, một lần nữa, bạn có thể sử dụng proc.communicate()
, mà chỉ đơn giản là làm read()
cho bạn.
Nếu bạn chỉ muốn chụp stderr
, nó hoạt động tương tự như với stdout
.
Có thêm một mẹo trước khi mọi thứ trở nên khó khăn. Giả sử bạn muốn chụp stdout
và cũng chụp stderr
nhưng trên cùng một đường ống như thiết bị xuất chuẩn:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Trong trường hợp này, subprocess
"gian lận"! Chà, nó phải làm điều này, vì vậy nó không thực sự gian lận: nó khởi động quy trình con với cả thiết bị xuất chuẩn và thiết bị xuất chuẩn của nó được đưa vào bộ mô tả đường ống (đơn) cung cấp lại cho quy trình cha mẹ (Python). Về phía phụ huynh, lại chỉ có một bộ mô tả ống duy nhất để đọc đầu ra. Tất cả đầu ra "stderr" hiển thị trong proc.stdout
và nếu bạn gọi proc.communicate()
, kết quả stderr (giá trị thứ hai trong tuple) sẽ None
không phải là một chuỗi.
Các trường hợp khó: hai hoặc nhiều ống
Tất cả các vấn đề xảy ra khi bạn muốn sử dụng ít nhất hai ống. Trong thực tế, subprocess
bản thân mã có bit này:
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:
Nhưng, than ôi, ở đây chúng tôi đã thực hiện ít nhất hai, và có thể ba, các đường ống khác nhau, vì vậy count(None)
lợi nhuận là 1 hoặc 0. Chúng tôi phải làm mọi thứ một cách khó khăn.
Trên Windows, điều này sử dụng threading.Thread
để tích lũy kết quả cho self.stdout
và self.stderr
, và có luồng gốc cung cấp self.stdin
dữ liệu đầu vào (và sau đó đóng đường ống).
Trên POSIX, điều này sử dụng poll
nếu có, nếu không select
, để tích lũy đầu ra và cung cấp đầu vào stdin. Tất cả điều này chạy trong tiến trình / luồng cha (đơn).
Chủ đề hoặc thăm dò ý kiến / chọn là cần thiết ở đây để tránh bế tắc. Ví dụ, giả sử rằng chúng tôi đã chuyển hướng cả ba luồng thành ba đường ống riêng biệt. Giả sử thêm rằng có một giới hạn nhỏ về số lượng dữ liệu có thể được nhét vào một đường ống trước khi quá trình viết bị đình chỉ, chờ quá trình đọc "dọn sạch" đường ống từ đầu kia. Chúng ta hãy đặt giới hạn nhỏ đó thành một byte đơn, chỉ để minh họa. (Đây thực tế là cách mọi thứ hoạt động, ngoại trừ giới hạn lớn hơn nhiều so với một byte.)
Nếu phụ huynh (Python) quá trình cố gắng để viết một vài byte-nói, 'go\n'
để proc.stdin
, byte đầu tiên đi vào và sau đó thứ hai gây ra quá trình Python để đình chỉ, chờ đợi tiến trình con để đọc các byte đầu tiên, đổ ống.
Trong khi đó, giả sử quy trình con quyết định in một câu thân thiện "Xin chào! Đừng hoảng sợ!" Lời chào. Nó H
đi vào đường ống cứng nhất của nó, nhưng e
nguyên nhân khiến nó bị đình chỉ, chờ cha mẹ đọc nó H
, làm trống đường ống cứng.
Bây giờ chúng tôi bị kẹt: quá trình Python đang ngủ, chờ kết thúc bằng cách nói "đi" và quy trình con cũng đang ngủ, chờ kết thúc để nói "Xin chào! Đừng hoảng sợ!".
Các subprocess.Popen
đang tránh vấn đề này với luồng-hoặc-select / thăm dò. Khi byte có thể đi qua các đường ống, họ đi. Khi họ không thể, chỉ một luồng (không phải toàn bộ quá trình) phải ngủ ngủ hoặc trong trường hợp chọn / thăm dò ý kiến, quy trình Python chờ đồng thời để "có thể ghi" hoặc "có sẵn dữ liệu", ghi vào stdin của quy trình chỉ khi có chỗ, và chỉ đọc thiết bị xuất chuẩn và / hoặc thiết bị xuất chuẩn khi dữ liệu đã sẵn sàng. Các proc.communicate()
mã (thực sự _communicate
nơi các trường hợp lông được xử lý) trở lại một lần toàn bộ dữ liệu stdin (nếu có) đã được gửi và tất cả dữ liệu stdout và / hoặc stderr đã được tích lũy.
Nếu bạn muốn đọc cả hai stdout
và stderr
trên hai đường ống khác nhau (bất kể stdin
chuyển hướng nào ), bạn cũng sẽ cần tránh bế tắc. Kịch bản bế tắc ở đây là khác nhau, nó xảy ra khi quy trình con viết một cái gì đó dài stderr
trong khi bạn lấy dữ liệu từ đó stdout
, hoặc ngược lại, nhưng nó vẫn ở đó.
Bản demo
Tôi đã hứa sẽ chứng minh rằng, không được chuyển hướng, Python subprocess
es ghi vào thiết bị xuất chuẩn bên dưới, không sys.stdout
. Vì vậy, đây là một số mã:
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()
Khi chạy:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Lưu ý rằng thường trình đầu tiên sẽ thất bại nếu bạn thêm stdout=sys.stdout
, vì một StringIO
đối tượng không có fileno
. Thứ hai sẽ bỏ qua hello
nếu bạn thêm stdout=sys.stdout
vì sys.stdout
đã được chuyển hướng đến os.devnull
.
(Nếu bạn chuyển hướng mô tả tệp của Python-1, quy trình con sẽ theo chuyển hướng đó. Cuộc open(os.devnull, 'w')
gọi tạo ra một luồng có fileno()
lớn hơn 2.)
Popen.poll
như trong câu hỏi Stack Overflow trước đó .