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 Popenchứ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, stdoutvà stderrnhư 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
intgiá trị. Đây là một mô tả tệp "thô" (ít nhất là trong POSIX). (Lưu ý bên: PIPEvà STDOUTthực sự intlà 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
filenophương thức. Popensẽ tìm mô tả cho luồng đó, sử dụng stream.fileno()và sau đó tiến hành như một intgiá trị.
subprocess.PIPE, chỉ ra rằng Python sẽ tạo ra một đường ống.
subprocess.STDOUT( stderrchỉ 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 Nonegiá 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 Nonegiá trị mặc định hoặc cung cấp rõ ràng None), Pipethì đ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 inthoặ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, Pipevẫ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 để stdoutvà stderrkhô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.writehiệ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 stdoutnhưng để lại stdinvà stderrmộ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 stdoutvà cũng chụp stderrnhư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.stdoutvà nếu bạn gọi proc.communicate(), kết quả stderr (giá trị thứ hai trong tuple) sẽ Nonekhô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ế, subprocessbả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.stdoutvà self.stderr, và có luồng gốc cung cấp self.stdindữ liệu đầu vào (và sau đó đóng đường ống).
Trên POSIX, điều này sử dụng pollnế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 enguyê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ự _communicatenơ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 stdoutvà stderrtrên hai đường ống khác nhau (bất kể stdinchuyể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 stderrtrong 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 subprocesses 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 hellonếu bạn thêm stdout=sys.stdoutvì 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.pollnhư trong câu hỏi Stack Overflow trước đó .