Bạn có thể thực hiện việc này với subprocess
, nhưng không hề nhỏ. Nếu bạn nhìn vào các tài liệu Frequently Used Arguments trong tài liệu, bạn sẽ thấy rằng bạn có thể vượt qua PIPE
làm đối số stderr
, tạo một đường ống mới, chuyển một bên của đường ống đến quy trình con và làm cho mặt kia có sẵn để sử dụng thuộc tính stderr
. *
Vì vậy, bạn sẽ cần phải phục vụ ống đó, ghi vào màn hình và vào tệp. Nói chung, nhận được các chi tiết phù hợp cho điều này là rất phức tạp. ** Trong trường hợp của bạn, chỉ có một ống, và bạn đang lập kế hoạch phục vụ nó đồng bộ, vì vậy nó không phải là xấu.
import subprocess
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
for line in proc.stderr:
sys.stdout.write(line)
log_file.write(line)
proc.wait()
(Lưu ý rằng có một số vấn đề sử dụng for line in proc.stderr:
-basically, nếu những gì bạn đang đọc hóa ra không phải là dòng đệm vì lý do bất kỳ, bạn có thể ngồi xung quanh chờ đợi một dòng mới mặc dù có thực sự là một nửa Bạn có thể đọc các khối dữ liệu để xử lý, ví dụ: read(128)
hoặc thậm chí read(1)
, để có được dữ liệu trơn tru hơn nếu cần. Nếu bạn thực sự nhận được mọi byte ngay khi nó đến và có thể không đủ khả năng chi phí của read(1)
, bạn sẽ cần đặt ống ở chế độ không chặn và đọc không đồng bộ.)
Nhưng nếu bạn đang sử dụng Unix, có thể đơn giản hơn khi sử dụng lệnh tee
để thực hiện điều đó cho bạn.
Để có giải pháp dơ bẩn & nhanh chóng, bạn có thể sử dụng vỏ để đi qua nó. Một cái gì đó như thế này:
subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True,
stdout=file_out)
Nhưng tôi không muốn gỡ lỗi đường ống vỏ; chúng ta hãy làm điều đó bằng Python, như in the docs:
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stderr)
tool.stderr.close()
tee.communicate()
Cuối cùng, có một tá hoặc nhiều giấy gói cao cấp xung quanh subprocesses và/hoặc vỏ trên PyPI- sh
, shell
, shell_command
, shellout
, iterpipes
, sarge
, cmd_utils
, commandwrapper
, v.v. Tìm kiếm "shell", "subprocess", "process", "command line", v.v ... và tìm một thứ bạn thích làm cho vấn đề tầm thường.
Nếu bạn cần thu thập cả stderr và stdout thì sao?
Cách dễ dàng để thực hiện việc này là chuyển hướng người này sang người khác, như Sven Marnach gợi ý trong một nhận xét. Chỉ cần thay đổi các thông số Popen
như thế này:.
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Và sau đó ở khắp mọi nơi bạn sử dụng tool.stderr
, sử dụng tool.stdout
thay-ví dụ, cho ví dụ cuối cùng:
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stdout)
tool.stdout.close()
tee.communicate()
Nhưng điều này có một số cân bằng. Rõ ràng nhất, trộn hai dòng với nhau có nghĩa là bạn không thể đăng nhập stdout để file_out và stderr để log_file, hoặc sao chép stdout để stdout của bạn và stderr để stderr của bạn. Nhưng nó cũng có nghĩa là thứ tự có thể không xác định - nếu tiến trình con luôn ghi hai dòng vào stderr trước khi viết bất cứ thứ gì để stdout, bạn có thể sẽ nhận được một loạt các stdout giữa hai dòng đó khi bạn trộn các dòng. Và nó có nghĩa là họ phải chia sẻ chế độ đệm của stdout, vì vậy nếu bạn dựa vào thực tế là linux/glibc đảm bảo stderr được line-buffered (trừ khi subprocess thay đổi rõ ràng nó), điều đó có thể không còn đúng nữa.
Nếu bạn cần xử lý riêng hai quy trình, việc này sẽ khó khăn hơn. Trước đó, tôi nói rằng phục vụ các đường ống trên bay là dễ dàng miễn là bạn chỉ có một đường ống và có thể phục vụ nó đồng bộ. Nếu bạn có hai đường ống, điều đó rõ ràng là không còn đúng nữa. Hãy tưởng tượng bạn đang chờ đợi trên tool.stdout.read()
và dữ liệu mới đến từ tool.stderr
. Nếu có quá nhiều dữ liệu, nó có thể làm cho đường ống tràn và quá trình con để chặn. Nhưng ngay cả khi điều đó không xảy ra, bạn rõ ràng sẽ không thể đọc và đăng nhập dữ liệu stderr cho đến khi một cái gì đó đến từ stdout.
Nếu bạn sử dụng giải pháp đường ống thông qua tee
, tránh được vấn đề ban đầu… nhưng chỉ bằng cách tạo dự án mới tồi tệ. Bạn có hai trường hợp tee
và trong khi bạn đang gọi communicate
trên một bản sao, một trường hợp khác đang ngồi chờ đợi mãi mãi.
Vì vậy, dù bằng cách nào, bạn cũng cần một số loại cơ chế không đồng bộ. Bạn có thể làm điều này với chủ đề, một lò phản ứng select
, giống như gevent
, v.v.
Dưới đây là một ví dụ nhanh chóng và bẩn:
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def tee_pipe(pipe, f1, f2):
for line in pipe:
f1.write(line)
f2.write(line)
t1 = threading.Thread(target=tee_pipe, args=(proc.stdout, file_out, sys.stdout))
t2 = threading.Thread(target=tee_pipe, args=(proc.stderr, log_file, sys.stderr))
t3 = threading.Thread(proc.wait)
t1.start(); t2.start(); t3.start()
t1.join(); t2.join(); t3.join()
Tuy nhiên, có một số trường hợp cạnh nơi mà sẽ không hoạt động. (Vấn đề là thứ tự mà SIGCHLD và SIGPIPE/EPIPE/EOF đến. Tôi không nghĩ bất kỳ thứ gì sẽ ảnh hưởng đến chúng tôi ở đây, vì chúng tôi không gửi bất kỳ thông tin nào ... nhưng đừng tin tôi thông qua và/hoặc kiểm tra.) Hàm subprocess.communicate
từ 3.3+ nhận được tất cả các chi tiết khó sử dụng. Nhưng bạn có thể thấy nó đơn giản hơn rất nhiều khi sử dụng một trong các trình cài đặt wrapper async-subprocess mà bạn có thể tìm thấy trên PyPI và ActiveState, hoặc thậm chí các công cụ con của quy trình con từ một khung công tác không chính thức như Twisted.
* Các tài liệu không thực sự giải thích những gì ống là, như thể họ mong đợi bạn là một tay Unix C cũ ... Nhưng một số ví dụ, đặc biệt là trong phần Replacing Older Functions with the subprocess
Module, cho thấy làm thế nào họ đang được sử dụng và nó khá đơn giản.
** Phần cứng là giải trình tự hai hoặc nhiều ống đúng cách. Nếu bạn chờ đợi trên một đường ống, người kia có thể tràn và chặn, ngăn cản sự chờ đợi của bạn trên một trong những khác từ bao giờ kết thúc. Cách dễ dàng nhất để giải quyết vấn đề này là tạo một luồng để phục vụ từng đường ống. (Trên hầu hết các nền tảng * nix, bạn có thể sử dụng lò phản ứng select
hoặc poll
để thay thế, nhưng làm cho nền tảng chéo đó trở nên khó khăn một cách đáng kinh ngạc.) The source cho mô-đun, đặc biệt là communicate
và người trợ giúp của nó. (Tôi liên kết với 3,3, bởi vì trong các phiên bản trước đó, communicate
chính nó nhận được một số điều quan trọng sai ...) Đây là lý do tại sao, bất cứ khi nào có thể, bạn muốn sử dụng communicate
nếu bạn cần nhiều hơn một đường ống. Trong trường hợp của bạn, bạn không thể sử dụng communicate
, nhưng may mắn thay bạn không cần nhiều hơn một đường ống.
Liệu mã của bạn cần phải làm việc trên Windows (hoặc các nền tảng khác không POSIXy)? Nếu không, có một câu trả lời dễ dàng hơn. – abarnert
Nó không cần phải! –
liên quan: [Python subprocess có đầu ra của trẻ em để tập tin và thiết bị đầu cuối?] (Http://stackoverflow.com/q/4984428/4279) – jfs