프로세스가 실행되는 동안 지속적으로 하위 프로세스 출력을 인쇄


201

파이썬 스크립트에서 프로그램을 시작하려면 다음 방법을 사용하고 있습니다.

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)

따라서와 같은 프로세스를 시작 Process.execute("mvn clean install")하면 프로세스가 완료 될 때까지 프로그램이 대기하고 프로그램의 전체 출력을 얻습니다. 완료하는 데 시간이 걸리는 프로세스를 실행하는 경우 짜증이납니다.

루프 또는 무언가가 끝나기 전에 프로세스 출력을 폴링하여 프로그램이 한 줄씩 프로세스 출력을 작성할 수 있습니까?

** [편집] 죄송합니다.이 질문을 게시하기 전에 검색이 잘되지 않았습니다. 스레딩이 실제로 핵심입니다. 방법을 보여주는 예제를 여기에서 찾았습니다 : ** Python Subprocess. 스레드에서 열기


하위 프로세스 대신 스레드, 나는 생각한다
개미

9
아니요, 스레드가 필요하지 않습니다. 전체 배관 아이디어는 작동하는 동안 프로세스에서 읽기 / 쓰기를 얻을 수 있기 때문에 효과적입니다.
tokland

답변:


264

명령이 출력하는 즉시 iter 를 사용 하여 행을 처리 할 수 있습니다 lines = iter(fd.readline, ""). 다음은 일반적인 사용 사례를 보여주는 전체 예입니다 (도움을 주신 @jfs 덕분에).

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
나는이 코드 (시행하는 데 상당한 시간이 걸리는 프로그램으로)를 시도했으며 실행이 완료되기를 기다리지 않고 수신 된 라인을 출력하는지 확인할 수 있습니다. 이것이 탁월한 답변입니다.
Andrew Martin

11
참고 : Python 3에서는을 사용할 수 있습니다 for line in popen.stdout: print(line.decode(), end=''). 파이썬 2와 3을 모두 지원하려면 바이트 리터럴을 사용하십시오. b''그렇지 않으면 lines_iterator파이썬 3에서 끝나지 않습니다 .
jfs

3
이 접근법의 문제점은 프로세스가 stdout에 아무것도 쓰지 않고 비트 동안 일시 정지하면 더 이상 읽을 입력이 없다는 것입니다. 프로세스가 완료되었는지 확인하려면 루프가 필요합니다. 나는 파이썬 2.7에서 subprocess32를 사용하여 이것을 시도했다
Har

7
작동해야합니다. 그것을 연마하기 위해, 당신은 추가 할 수 있습니다 bufsize=1(이것은 파이썬 2의 성능을 향상시킬 수 있습니다)를 가까이 popen.stdout파이프 명시 적으로 (알아서하기 위해 쓰레기 수거를 기다리지 않고)과 인상 subprocess.CalledProcessError(같은 check_call(), check_output()할). 이 print문장은 Python 2와 3에서 다릅니다 print line,. 코드와 같이 줄 바꿈을 두 배로 늘리고 universal_newlines=TruePython 3을 전달하지 않고 바이트 와 관련된 텍스트를 얻기 위해 softspace hack (주 : 쉼표)을 사용할 수 있습니다 .
jfs 2016 년

6
@binzhang 오류가 아닙니다. stdout은 기본적으로 Python 스크립트에서 버퍼링됩니다 (많은 Unix 도구). 시도하십시오 execute(["python", "-u", "child_thread.py"]). 추가 정보 : stackoverflow.com/questions/14258500/…
tokland

84

좋아, 나는이 질문에서 조각을 사용하여 (어떤 제안 스레드를 사용하는 이유는 더 나은 감사합니다 것) 스레드없이 그것을 해결하기 위해 관리 가 실행되는 동안 서브 프로세스의 표준 출력을 가로 채기를

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
ifischer와 tokland의 코드를 병합하는 것은 매우 잘 작동합니다 (로 변경 print line,해야 sys.stdout.write(nextline); sys.stdout.flush()합니다. 그렇지 않으면 두 줄마다 인쇄됩니다. 다시 한 번, 이것은 IPython의 Notebook 인터페이스를 사용하므로 flush()작업이 명시 적으로 호출 되든 상관없이 다른 일이 발생했을 수 있습니다 .
eacousineau

3
미스터 당신은 내 생명의 은인입니다 !! 이런 종류의 것들이 라이브러리 자체에 내장되어 있지 않다는 것이 정말 이상합니다. cliapp를 작성하면, 루프에서 처리중인 모든 것을 즉시 보여주고 싶습니다 .. s'rsly ..
holms

3
이 솔루션은 지속적으로 인쇄 수정할 수 있습니다 모두 출력 및 오류를? 내가 변경하는 경우 stderr=subprocess.STDOUTstderr=subprocess.PIPE다음 전화를 process.stderr.readline()루프 내에서, 나는 충돌한다 매우의 실행이이 설명서에 대한 경고입니다 교착 것 같다 subprocess모듈.
davidrmcharles

7
@DavidCharles 나는 당신이 찾고있는 것이 stdout=subprocess.PIPE,stderr=subprocess.STDOUTstderr을 캡처한다고 생각하며 stdin을 캡처한다고 생각합니다 (그러나 테스트하지는 않았습니다).
Andrew Martin

종료 코드를 기다려 주셔서 감사합니다. 그것을 해결하는 방법을 몰랐다
Vitaly Isaev

67

파이썬 3에서 stdout 버퍼가 플러시되는 즉시 서브 프로세스의 출력을 라인별로 인쇄하려면 :

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)

주의 : 필요하지 않습니다 p.poll(). eof에 도달하면 루프가 종료됩니다. 그리고 당신은 필요하지 않습니다iter(p.stdout.readline, '') - 미리 읽기 버그는 파이썬 3에서 해결되었습니다.

Python : subprocess.communicate ()에서 스트리밍 입력 읽기를 참조하십시오 .


3
이 솔루션은 저에게 효과적이었습니다. 위에서 주어진 수용 솔루션은 나를 위해 빈 줄을 인쇄했습니다.
코드 이름

3
즉시 인쇄하려면 sys.stdout.flush ()를 추가해야했습니다.
코드 이름

3
@Codename : sys.stdout.flush()부모에서 필요하지 않아야합니다 .stdout 은 파일 / 파이프로 리디렉션되지 않으면 라인 버퍼링되어 인쇄 line가 버퍼를 자동으로 플러시합니다. 당신은 sys.stdout.flush()아이에게도 필요하지 않습니다 - -u대신 명령 줄 옵션을 전달하십시오 .
jfs

1
@Codename : 사용하려면 >을 실행하십시오 python -u your-script.py > some-file. 주의 사항 : -u위에서 언급 한 옵션 (을 사용할 필요가 없음 sys.stdout.flush()).
jfs

1
@mvidelgauz p.wait()는 전화 할 필요가 없습니다 with. 블록 에서 나갈 때 호출됩니다 . 사용하십시오 p.returncode.
jfs

8

실제로 출력 을 인쇄 하고 싶을 때 이렇게하는 간단한 방법이 있습니다 .

import subprocess
import sys

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

여기서 우리는 단순히 하위 프로세스를 자체 stdout을 가리키고 기존 성공 또는 예외 API를 사용합니다.


1
이 솔루션은 Python 3.6의 경우 @tokland의 솔루션보다 간단하고 깨끗합니다. shell = True 인수가 필요하지 않은 것으로 나타났습니다.
Good Will

잘 잡아라 제거shell=True
Andrew Ring

매우 어리 석고 작은 코드로 완벽하게 작동합니다. 어쩌면 하위 프로세스 stderr을 sys.stderr로 리디렉션해야합니까?
마누

확실히 할 수 있습니다. 질문에 대한 시도가 stderr을 stdout으로 리디렉션했기 때문에 여기에 있지 않았습니다.
Andrew Ring

sys.stdout과 subprocess.STDOUT의 차이점을 설명 할 수 있습니까?
Ron Serruya

7

@tokland

코드를 시도하고 3.4로 수정했으며 Windows dir.cmd는 cmd-file로 저장된 간단한 dir 명령입니다.

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
당신은 할 수 코드를 단순화 . iter()그리고 end='\r\n'불필요하다. 파이썬은 기본적으로 유니버설 개행 모드를 사용합니다. 즉, 모든 '\n'것이 '\r\n'인쇄 중에 변환됩니다 . 'latin'아마도 잘못된 인코딩 일 것 universal_newlines=True입니다. 파이썬 3에서 텍스트 출력을 얻는 데 사용할 수 있습니다 (로케일의 선호하는 인코딩을 사용하여 디코딩 됨). 에 멈추지 마십시오 .poll(). 읽지 않은 버퍼링 된 데이터가있을 수 있습니다. Python 스크립트가 콘솔에서 실행 중이면 해당 출력이 라인 버퍼링됩니다. -u옵션을 사용하여 라인 버퍼링을 강제 할 수 flush=True있습니다. 여기서는 필요하지 않습니다 .
jfs

4

누군가가 두에서 읽고 싶은 경우 stdoutstderr스레드를 사용하여 동시에, 이것이 내가 생각 해낸 것입니다 :

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)

나는이 질문에 비슷한 것을하려고 노력 하면서이 사실을 공유하고 싶었지만 대답으로 내 문제를 해결하지 못했습니다. 잘만되면 그것은 누군가를 돕는다!

내 유스 케이스에서 외부 프로세스가 우리 프로세스를 종료 Popen()시킵니다.


1
python2에 대해 이와 거의 비슷한 것을 사용해야했습니다. python2에서 이와 같은 것이 제공되어야하지만, 이와 같은 것은 절대적으로 좋지 않습니다.
스튜어트 Axon

3

파이썬 스크립트에서 stdout을 얻기 위해이 질문에 대한 답변을 시도하는 사람은 Python이 stdout을 버퍼링하므로 stdout을 보는 데 시간이 걸릴 수 있습니다.

대상 스크립트에서 각 stdout 쓰기 후에 다음을 추가하여이를 수정할 수 있습니다.

sys.stdout.flush()

1
그러나 파이썬을 파이썬의 하위 프로세스로 실행하는 것은 처음에는 미친 일입니다. 스크립트는 단순히 import다른 스크립트 여야합니다 . 조사 multiprocessing또는 threading당신은 병렬화 실행을 필요로하는 경우.
tripleee

3
@triplee 파이썬을 파이썬의 서브 프로세스로 실행하는 것이 몇 가지 시나리오가 있습니다. 매일 순차적으로 실행하려는 많은 파이썬 배치 스크립트가 있습니다. 이것들은 실행을 시작하는 마스터 파이썬 스크립트로 조정될 수 있으며 자식 스크립트가 실패하면 이메일을 보냅니다. 각 스크립트는 다른 스크립트와 샌드 박스 처리되므로 이름 충돌이 없습니다. 나는 병렬 처리하지 않으므로 다중 처리 및 스레딩은 관련이 없습니다.
user1379351

예를 들어, 메인 파이썬 프로그램이 실행되고있는 것과 다른 파이썬 실행 파일을 사용하여 다른 파이썬 프로그램을 시작할 수도 있습니다.subprocess.run("/path/to/python/executable", "pythonProgramToRun.py")
Kyle Bridenstine

3

Python> = 3.5에서는 subprocess.run나를 위해 작동합니다.

import subprocess

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

https://docs.python.org/3/library/subprocess.html#subprocess.run ( 실행 중에 출력을 얻는 것도 작동하지 않습니다 shell=True)


2
이것은 "실행 중"이 아닙니다. subprocess.run()서브 프로세스가 완료 실행하고있는 경우 호출 만 반환합니다.
tripleee

1
"실행 중"이 아닌 방법을 설명 할 수 있습니까? 비슷해 >>> import subprocess; subprocess.run('top')도는 "실행 중"인쇄하는 것 (그리고 상위 완료되지 않음). 어쩌면 나는 미묘한 차이를 파악하지 못하고 있습니까?
user7017793

예를 들어 출력을 파이썬으로 다시 리디렉션하면 완료 stdout=subprocess.PIPE후에 만 읽을 수 있습니다 top. 하위 프로세스를 실행하는 동안 Python 프로그램이 차단되었습니다.
tripleee

1
맞습니다. run만에 관심이 있다면 방법은 여전히 작동 보고 가 생성됩니다으로 출력합니다. 파이썬에서 출력으로 비동기식으로 무언가를하고 싶다면 작동하지 않는 것이 맞습니다.
user7017793

3

원래의 질문에 대답하기 위해 IMO가 하위 프로세스 stdout를 프로그램으로 직접 리디렉션하는 가장 좋은 방법입니다 stdout(선택적 stderr으로 아래 예와 같이 동일하게 수행 할 수 있음 )

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

3
아무것도를 지정하지 않으면 stdoutstderr적은 코드와 같은 일을한다. 비록 명시 적이 암시 적보다 낫다고
tripleee

1

이 PoC는 지속적으로 프로세스의 출력을 읽고 필요할 때 액세스 할 수 있습니다. 마지막 결과 만 유지되고 다른 모든 출력은 삭제되므로 PIPE가 메모리에서 커지는 것을 방지합니다.

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)

출력 : ~ 2.5 초 간격의 출력 만 없음을 분명히 알 수 있습니다.

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

0

이것은 적어도 Python3.4에서 작동합니다.

import subprocess

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

1
이것은 프로세스 실행이 끝날 때까지 루프에서 차단되는 문제가 있습니다.
tripleee

0

여기에 대한 답변 중 어느 것도 나의 모든 요구를 해결하지 못했습니다.

  1. stdout에 대한 스레드가 없습니다 (큐 등도 없음)
  2. 다른 일이 있는지 확인해야 할 때 비 차단
  3. 스트림 출력, 로그 파일에 쓰기 및 출력의 문자열 복사본을 반환하는 등 여러 작업을 수행하는 데 필요한 PIPE를 사용하십시오.

약간의 배경 : ThreadPoolExecutor를 사용하여 스레드 풀을 관리하고 있습니다. 각 스레드는 하위 프로세스를 시작하고 동시성을 실행합니다. (Python2.7에서는 최신 3.x에서도 작동합니다). 나는 다른 것들을 위해 가능한 많은 것을 원하기 때문에 출력 수집을 위해 스레드를 사용하고 싶지 않습니다 (20 개의 프로세스 풀은 40 개의 스레드를 사용하여 실행됩니다 .1은 프로세스 스레드와 1은 stdout입니다 ... stderr를 원한다면 더 많이 추측합니다)

나는 많은 예외를 제거하고 있으며 여기서는 프로덕션에서 작동하는 코드를 기반 으로합니다. 잘만되면 나는 그것을 복사하여 붙여 넣기를 망치지 않았습니다. 또한 피드백은 대단히 환영합니다!

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

여기에 오버 헤드가 추가 될 것이라고 확신하지만 내 경우에는 문제가되지 않습니다. 기능적으로 그것은 내가 필요한 일을합니다. 내가 해결하지 않은 유일한 것은 이것이 로그 메시지에 완벽하게 작동하는 이유이지만 print나중에 일부 메시지가 한 번에 표시되는 것을 볼 수 있습니다 .


-2

파이썬 3.6에서는 이것을 사용했습니다 :

import subprocess

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

1
이것은이 특정한 질문에 대한 답이 아닙니다. 출력을 얻기 전에 서브 프로세스가 완료되기를 기다리는 것은 OP가 피하려고하는 구체적이고 정확하게입니다. 구식 레거시 함수 subprocess.call()에는 새로운 함수로 수정 ​​된 약간의 사마귀가 있습니다. 파이썬 3.6에서는 일반적으로 subprocess.run()이것을 사용 합니다; 편의상 이전 래퍼 함수 subprocess.check_output()도 계속 사용할 수 있습니다. 프로세스에서 실제 출력을 리턴합니다 (이 코드는 종료 코드 만 리턴하지만 정의되지 않은 것을 인쇄합니다).
tripleee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.