In liên tục đầu ra Sub process trong khi process đang chạy


201

Để khởi chạy các chương trình từ tập lệnh Python của tôi, tôi đang sử dụng phương pháp sau:

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

Vì vậy, khi tôi khởi chạy một quá trình như Process.execute("mvn clean install") , chương trình của tôi sẽ đợi cho đến khi quá trình kết thúc và chỉ sau đó tôi mới nhận được đầu ra hoàn chỉnh của chương trình. Điều này thật khó chịu nếu tôi đang chạy một quá trình mất một lúc để hoàn thành.

Tôi có thể để chương trình của mình viết dòng đầu ra của quy trình theo từng dòng không, bằng cách bỏ phiếu đầu ra quy trình trước khi nó kết thúc trong một vòng lặp hoặc một cái gì đó?

** [EDIT] Xin lỗi tôi đã không tìm kiếm rất tốt trước khi đăng câu hỏi này. Threading thực sự là chìa khóa. Tìm thấy một ví dụ ở đây chỉ ra cách thực hiện: ** Python Sub Process.Popen từ một luồng


Chủ đề thay vì quy trình con, tôi nghĩ
Ant

9
Không, bạn không cần chủ đề. Toàn bộ ý tưởng đường ống hoạt động vì bạn có thể nhận / đọc từ các quy trình trong khi chúng đang chạy.
tokland

Câu trả lời:


264

Bạn có thể sử dụng iter để xử lý các dòng ngay khi lệnh xuất chúng : lines = iter(fd.readline, ""). Dưới đây là một ví dụ đầy đủ cho thấy trường hợp sử dụng điển hình (cảm ơn @jfs đã giúp đỡ):

from __future__ import print_function # Only Python 2.x
import subprocess

def execute(cmd):
    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    for stdout_line in iter(popen.stdout.readline, ""):
        yield stdout_line 
    popen.stdout.close()
    return_code = popen.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

# Example
for path in execute(["locate", "a"]):
    print(path, end="")

24
Tôi đã thử mã này (với một chương trình cần nhiều thời gian để chạy) và có thể xác nhận nó xuất ra các dòng khi chúng nhận được, thay vì chờ thực thi hoàn tất. Đây là câu trả lời vượt trội imo.
Andrew Martin

11
Lưu ý: Trong Python 3, bạn có thể sử dụng for line in popen.stdout: print(line.decode(), end=''). Để hỗ trợ cả Python 2 và 3, hãy sử dụng byte bằng chữ: b''nếu không thì lines_iteratorkhông bao giờ kết thúc trên Python 3.
jfs

3
Vấn đề với cách tiếp cận này là nếu quá trình tạm dừng một chút mà không ghi bất cứ điều gì vào thiết bị xuất chuẩn thì không có thêm đầu vào để đọc. Bạn sẽ cần một vòng lặp để kiểm tra xem quá trình đã kết thúc hay chưa. Tôi đã thử điều này bằng cách sử dụng sub process32 trên python 2.7
Har

7
nó nên hoạt động Để đánh bóng nó, bạn có thể thêm bufsize=1(nó có thể cải thiện hiệu suất trên Python 2), đóng popen.stdoutđường ống một cách rõ ràng (không cần chờ bộ sưu tập rác chăm sóc nó) và nâng cao subprocess.CalledProcessError(như check_call(), check_output()làm). Các printtuyên bố là khác nhau trên Python 2 và 3: bạn có thể sử dụng hack softspace print line,(lưu ý: dấu phẩy) để tránh tăng gấp đôi tất cả các dòng mới như mã của bạn làm và đi universal_newlines=Truevề Python 3, để có được văn bản thay vì bytes- câu trả lời liên quan .
jfs

6
@binzhang Đó không phải là một lỗi, thiết bị xuất chuẩn được đệm theo mặc định trên các tập lệnh Python (cũng cho nhiều công cụ Unix). Hãy thử execute(["python", "-u", "child_thread.py"]). Thông tin thêm: stackoverflow.com/questions/14258500/
Kẻ

84

Ok tôi quản lý để giải quyết nó mà không có chủ đề (bất kỳ đề xuất nào tại sao sử dụng chủ đề sẽ được đánh giá cao hơn) bằng cách sử dụng một đoạn trích từ câu hỏi này Chặn stdout của một quy trình con trong khi nó đang chạy

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() is not None:
            break
        sys.stdout.write(nextline)
        sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

3
Sáp nhập mã của ifischer và tokland của hoạt động khá tốt (tôi đã phải thay đổi print line,để sys.stdout.write(nextline); sys.stdout.flush()Nếu không, nó sẽ in ra tất cả hai dòng Sau đó, một lần nữa, điều này được sử dụng giao diện Notebook IPython, vì vậy có lẽ cái gì khác đang xảy ra -.. Không phân biệt, gọi một cách rõ ràng flush()các công trình.
eacousineau

3
Ông là người cứu mạng tôi !! thực sự kỳ lạ rằng loại mọi thứ không build-in trong thư viện riêng của mình .. nguyên nhân nếu tôi viết cliapp, tôi muốn thể hiện tất cả mọi thứ những gì đang chế biến trong vòng lặp ngay lập tức .. s'rsly ..
Holms

3
Giải pháp này có thể được sửa đổi để liên tục in cả đầu ra và lỗi không? Nếu tôi thay đổi stderr=subprocess.STDOUTđến stderr=subprocess.PIPEvà sau đó gọi process.stderr.readline()từ bên trong vòng lặp, tôi dường như đụng chạm tới rất bế tắc được cảnh báo về trong tài liệu cho các subprocessmô-đun.
davidrmcharles 16/12/13

7
@DavidCharles Tôi nghĩ những gì bạn đang tìm kiếm là stdout=subprocess.PIPE,stderr=subprocess.STDOUTcái này bắt được stderr, và tôi tin rằng (nhưng tôi chưa thử nghiệm) rằng nó cũng chụp được stdin.
Andrew Martin

cảm ơn vì đã chờ mã thoát Không biết làm thế nào để giải quyết nó
Vitaly Isaev

67

Để in từng dòng đầu ra của quy trình con ngay khi bộ đệm xuất chuẩn của nó được xóa trong Python 3:

from subprocess import Popen, PIPE, CalledProcessError

with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='') # process line here

if p.returncode != 0:
    raise CalledProcessError(p.returncode, p.args)

Lưu ý: bạn không cần p.poll()- vòng lặp kết thúc khi đạt đến eof. Và bạn không cần iter(p.stdout.readline, '')- lỗi đọc trước được sửa trong Python 3.

Xem thêm, Python: đọc đầu vào truyền phát từ sub process.c truyền thông () .


3
Giải pháp này đã làm việc cho tôi. Các giải pháp được chấp nhận ở trên chỉ là in các dòng trống cho tôi.
Tên mã

3
Tôi đã phải thêm sys.stdout.flush () để có được bản in ngay lập tức.
Tên mã

3
@Codename: bạn không cần sys.stdout.flush()trong cha mẹ - thiết bị xuất chuẩn được đệm dòng nếu nó không được chuyển hướng đến một tệp / ống và do đó in linetự động xóa bộ đệm. Bạn cũng không cần sys.stdout.flush()ở trẻ - -uthay vào đó hãy chuyển tùy chọn dòng lệnh.
JFS

1
@Codename: nếu bạn muốn sử dụng >thì hãy chạy python -u your-script.py > some-file. Lưu ý: -utùy chọn mà tôi đã đề cập ở trên (không cần sử dụng sys.stdout.flush()).
JFS

1
@mvidelgauz không cần gọi Gọiitit p.wait()được gọi khi thoát khỏi withkhối. Sử dụng p.returncode.
jfs

8

Thực sự có một cách thực sự đơn giản để làm điều này khi bạn chỉ muốn in đầu ra:

import subprocess
import sys

def execute(command):
    subprocess.check_call(command, stdout=sys.stdout, stderr=subprocess.STDOUT)

Ở đây chúng tôi chỉ đơn giản là trỏ quy trình con vào thiết bị xuất chuẩn của riêng mình và sử dụng api thành công hoặc ngoại lệ hiện có.


1
Giải pháp này đơn giản và sạch hơn giải pháp của @ tokland, đối với Python 3.6. Tôi nhận thấy rằng shell = True argument là không cần thiết.
Thiện chí

Bắt tốt, ý chí tốt. Đã xóashell=True
Andrew Ring

Rất thông minh, và hoạt động hoàn hảo với ít mã. Có lẽ bạn cũng nên chuyển hướng stderr của quy trình con sang sys.stderr?
Manu

Manu bạn chắc chắn có thể. Tôi đã không, ở đây, bởi vì nỗ lực trong câu hỏi đã chuyển hướng stderr sang thiết bị xuất chuẩn.
Andrew Ring

Bạn có thể giải thích sự khác biệt giữa sys.stdout và sub process.STDOUT không?
Ron Serruya

7

@tokland

đã thử mã của bạn và sửa nó cho 3,4 và windows dir.cmd là một lệnh dir đơn giản, được lưu dưới dạng tệp cmd

import subprocess
c = "dir.cmd"

def execute(command):
    popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
    lines_iterator = iter(popen.stdout.readline, b"")
    while popen.poll() is None:
        for line in lines_iterator:
            nline = line.rstrip()
            print(nline.decode("latin"), end = "\r\n",flush =True) # yield line

execute(c)

3
bạn có thể đơn giản hóa mã của bạn . iter()end='\r\n'không cần thiết Python sử dụng chế độ dòng mới phổ quát theo mặc định, tức là bất kỳ '\n'được dịch sang '\r\n'trong khi in. 'latin'có thể là mã hóa sai, bạn có thể sử dụng universal_newlines=Trueđể nhận đầu ra văn bản trong Python 3 (được giải mã bằng mã hóa ưa thích của miền địa phương). Đừng dừng lại .poll(), có thể có bộ đệm dữ liệu chưa đọc. Nếu tập lệnh Python đang chạy trong bàn điều khiển thì đầu ra của nó được đệm dòng; bạn có thể buộc -utùy chọn sử dụng bộ đệm dòng - bạn không cần flush=Trueở đây.
jfs

4

Trong trường hợp ai đó muốn đọc từ cả hai stdoutstderrcùng lúc sử dụng các chủ đề, đây là những gì tôi nghĩ ra:

import threading
import subprocess
import Queue

class AsyncLineReader(threading.Thread):
    def __init__(self, fd, outputQueue):
        threading.Thread.__init__(self)

        assert isinstance(outputQueue, Queue.Queue)
        assert callable(fd.readline)

        self.fd = fd
        self.outputQueue = outputQueue

    def run(self):
        map(self.outputQueue.put, iter(self.fd.readline, ''))

    def eof(self):
        return not self.is_alive() and self.outputQueue.empty()

    @classmethod
    def getForFd(cls, fd, start=True):
        queue = Queue.Queue()
        reader = cls(fd, queue)

        if start:
            reader.start()

        return reader, queue


process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)

# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
   # Process all available lines from the stdout Queue.
   while not stdoutQueue.empty():
       line = stdoutQueue.get()
       print 'Received stdout: ' + repr(line)

       # Do stuff with stdout line.

   # Process all available lines from the stderr Queue.
   while not stderrQueue.empty():
       line = stderrQueue.get()
       print 'Received stderr: ' + repr(line)

       # Do stuff with stderr line.

   # Sleep for a short time to avoid excessive CPU use while waiting for data.
   sleep(0.05)

print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()

# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()

print "Waiting for process to exit..."
returnCode = process.wait()

if returnCode != 0:
   raise subprocess.CalledProcessError(returnCode, command)

Tôi chỉ muốn chia sẻ điều này, vì tôi đã kết thúc câu hỏi này khi cố gắng làm điều gì đó tương tự, nhưng không có câu trả lời nào giải quyết được vấn đề của tôi. Hy vọng nó sẽ giúp được ai đó!

Lưu ý rằng trong trường hợp sử dụng của tôi, một quy trình bên ngoài sẽ giết chết quy trình mà chúng ta Popen().


1
Tôi đã phải sử dụng một cái gì đó gần như chính xác như thế này cho python2. Mặc dù một cái gì đó như thế này nên được cung cấp trong python2, nhưng nó không phải là thứ gì đó như thế này là hoàn toàn tốt.
Stuart Axon

3

Đối với bất kỳ ai đang thử các câu trả lời cho câu hỏi này để nhận được thiết bị xuất chuẩn từ tập lệnh Python lưu ý rằng Python đệm thiết bị xuất chuẩn của nó, và do đó có thể mất một lúc để xem thiết bị xuất chuẩn.

Điều này có thể được sửa chữa bằng cách thêm vào sau đây sau mỗi lần ghi xuất chuẩn trong tập lệnh đích:

sys.stdout.flush()

1
Nhưng việc chạy Python như một quy trình con của Python là điên rồ ngay từ đầu. Kịch bản của bạn nên đơn giản là importkịch bản khác; nhìn vào multiprocessinghoặc threadingnếu bạn cần thực hiện song song.
tripleee

3
@triplee Có một số tình huống trong đó chạy Python như một quy trình con của Python là phù hợp. Tôi có một số tập lệnh bó python mà tôi muốn chạy tuần tự, hàng ngày. Chúng có thể được phối hợp bởi một tập lệnh Python chính khởi tạo việc thực thi và gửi email cho tôi nếu tập lệnh con không thành công. Mỗi tập lệnh được sandbox từ khác - không có xung đột đặt tên. Tôi không song song nên đa xử lý và phân luồng không liên quan.
dùng1379351

Bạn cũng có thể bắt đầu chương trình python khác bằng cách sử dụng một python khác với chương trình python chính đang chạy, ví dụ:subprocess.run("/path/to/python/executable", "pythonProgramToRun.py")
Kyle Bridenstine

3

Trong Python> = 3.5 sử dụng subprocess.runcông việc cho tôi:

import subprocess

cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)

(nhận đầu ra trong khi thực thi cũng hoạt động mà không có shell=True) https: //docs.python.org/3/l Library / sub process.html # sub process.run


2
Đây không phải là "trong khi thực hiện". Cuộc subprocess.run()gọi chỉ trả về khi quá trình con đã chạy xong.
tripleee

1
Bạn có thể giải thích làm thế nào nó không "trong khi thực hiện"? Một cái gì đó giống như >>> import subprocess; subprocess.run('top')cũng in "trong khi thực hiện" (và đầu trang không bao giờ kết thúc). Có lẽ tôi không nắm bắt được một số khác biệt tinh tế?
user7017793

Nếu bạn chuyển hướng đầu ra trở lại Python, ví dụ với stdout=subprocess.PIPEbạn chỉ có thể đọc nó sau khi topkết thúc. Chương trình Python của bạn bị chặn trong quá trình thực hiện quy trình con.
tripleee

1
Phải, điều đó có ý nghĩa. Các runphương pháp vẫn hoạt động nếu bạn chỉ quan tâm đến việc nhìn thấy đầu ra như nó được tạo ra. Nếu bạn muốn làm một cái gì đó với đầu ra trong python không đồng bộ, bạn có quyền rằng nó không hoạt động.
user7017793

3

Để trả lời câu hỏi ban đầu, cách tốt nhất IMO chỉ là chuyển hướng stdouttrực tiếp quy trình con đến chương trình của bạn stdout(tùy chọn, điều tương tự có thể được thực hiện stderr, như trong ví dụ dưới đây)

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()

3
Không chỉ định bất cứ điều gì cho stdoutstderrlàm điều tương tự với ít mã hơn. Mặc dù tôi cho rằng rõ ràng là tốt hơn so với ngầm.
tripleee

1

PoC này liên tục đọc đầu ra từ một quá trình và có thể được truy cập khi cần. Chỉ giữ lại kết quả cuối cùng, tất cả các đầu ra khác bị loại bỏ, do đó ngăn PIPE phát triển ra khỏi bộ nhớ:

import subprocess
import time
import threading
import Queue


class FlushPipe(object):
    def __init__(self):
        self.command = ['python', './print_date.py']
        self.process = None
        self.process_output = Queue.LifoQueue(0)
        self.capture_output = threading.Thread(target=self.output_reader)

    def output_reader(self):
        for line in iter(self.process.stdout.readline, b''):
            self.process_output.put_nowait(line)

    def start_process(self):
        self.process = subprocess.Popen(self.command,
                                        stdout=subprocess.PIPE)
        self.capture_output.start()

    def get_output_for_processing(self):
        line = self.process_output.get()
        print ">>>" + line


if __name__ == "__main__":
    flush_pipe = FlushPipe()
    flush_pipe.start_process()

    now = time.time()
    while time.time() - now < 10:
        flush_pipe.get_output_for_processing()
        time.sleep(2.5)

    flush_pipe.capture_output.join(timeout=0.001)
    flush_pipe.process.kill()

print_date.py

#!/usr/bin/env python
import time

if __name__ == "__main__":
    while True:
        print str(time.time())
        time.sleep(0.01)

đầu ra: Bạn có thể thấy rõ rằng chỉ có đầu ra từ khoảng ~ 2,5 giây không có gì ở giữa.

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01

0

Điều này hoạt động ít nhất trong Python3.4

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())

1
Điều này có vấn đề là nó chặn trong vòng lặp cho đến khi quá trình chạy xong.
tripleee

0

Không có câu trả lời nào ở đây giải quyết tất cả các nhu cầu của tôi.

  1. Không có chủ đề cho thiết bị xuất chuẩn (không có Hàng đợi, v.v.)
  2. Không chặn vì tôi cần kiểm tra những thứ khác đang diễn ra
  3. Sử dụng PIPE khi tôi cần để thực hiện nhiều việc, ví dụ đầu ra luồng, ghi vào tệp nhật ký và trả về một bản sao chuỗi của đầu ra.

Một chút nền tảng: Tôi đang sử dụng một ThreadPoolExecutor để quản lý một nhóm các luồng, mỗi luồng khởi chạy một tiến trình con và chạy chúng đồng thời. (Trong Python2.7, nhưng điều này cũng sẽ hoạt động trong 3.x mới hơn). Tôi không muốn sử dụng các luồng chỉ để thu thập đầu ra vì tôi muốn càng nhiều càng tốt cho các thứ khác (một nhóm gồm 20 quy trình sẽ sử dụng 40 luồng chỉ để chạy; 1 cho luồng xử lý và 1 cho đầu ra ... và nhiều hơn nữa nếu bạn muốn stderr tôi đoán)

Tôi đang gỡ lại rất nhiều ngoại lệ và như vậy ở đây vì vậy điều này dựa trên mã hoạt động trong sản xuất. Hy vọng tôi đã không làm hỏng nó trong bản sao và dán. Ngoài ra, thông tin phản hồi rất hoan nghênh!

import time
import fcntl
import subprocess
import time

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
    """A little inline function to handle the stdout business. """
    # fcntl makes readline non-blocking so it raises an IOError when empty
    try:
        for s in iter(proc_stream.readline, ''):   # replace '' with b'' for Python 3
            my_buffer.append(s)

            if echo_streams:
                sys.stdout.write(s)

            if log_file:
                log_file.write(s)
    except IOError:
        pass

# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
    handle_stdout(proc_stdout, stdout_parts)

    # ...Check for other things here...
    # For example, check a multiprocessor.Value('b') to proc.kill()

    time.sleep(0.01)

# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)

stdout_str = "".join(stdout_parts)  # Just to demo

Tôi chắc chắn có thêm chi phí được thêm vào đây nhưng nó không phải là vấn đề đáng lo ngại trong trường hợp của tôi. Về mặt chức năng nó làm những gì tôi cần. Điều duy nhất tôi chưa giải quyết được là tại sao điều này hoạt động hoàn hảo cho các thông điệp tường trình nhưng tôi thấy một số printtin nhắn xuất hiện sau đó và tất cả cùng một lúc.


-2

Trong Python 3.6 tôi đã sử dụng điều này:

import subprocess

cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)

1
Đây không phải là một câu trả lời cho câu hỏi đặc biệt này. Chờ cho quá trình con kết thúc trước khi có được đầu ra của nó là cụ thể và chính xác những gì OP đang cố gắng tránh. Chức năng cũ subprocess.call()có một số mụn cóc được cố định bởi các chức năng mới hơn; trong Python 3.6 bạn thường sử dụng subprocess.run()cho việc này; để thuận tiện, chức năng trình bao bọc cũ hơn subprocess.check_output()vẫn có sẵn - nó trả về đầu ra thực tế từ quy trình (mã này sẽ chỉ trả về mã thoát, nhưng ngay cả sau đó in một cái gì đó không xác định thay thế).
tripleee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.