하위 프로세스를 사용하여 실시간 출력 얻기


135

작업에 대한 진행률 표시기가 표시되는 명령 줄 프로그램 (svnadmin verify)에 대한 래퍼 스크립트를 작성하려고합니다. 이렇게하면 랩핑 된 프로그램의 출력 라인이 출력되는 즉시 볼 수 있어야합니다.

나는을 사용하여 프로그램을 실행하고 사용 subprocess.Popen하고 stdout=PIPE각 줄을 읽은 다음 그에 따라 행동한다고 ​​생각했다. 그러나 다음 코드를 실행하면 출력이 어딘가에 버퍼링되어 1 ~ 332 줄, 333 ~ 439 (마지막 출력 줄)의 두 덩어리로 나타납니다.

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, shell = True)
for line in p.stdout:
    print line.replace('\n', '')

하위 프로세스에 대한 문서를 약간 살펴본 후 bufsize매개 변수를로 찾았 Popen으므로 bufsize를 1 (각 행 버퍼) 및 0 (버퍼 없음)으로 설정하려고 시도했지만 값이 행 전달 방식을 변경하지 않는 것 같습니다.

이 시점에서 나는 빨대를 파악하기 시작했으며 다음과 같은 출력 루프를 작성했습니다.

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

그러나 같은 결과를 얻었습니다.

하위 프로세스를 사용하여 실행 된 프로그램의 '실시간'프로그램 출력을 얻을 수 있습니까? 파이썬에서 순방향 호환 (아닌 exec*) 다른 옵션이 있습니까?


1
sydout=PIPE부모 프로세스를 무시하고 하위 프로세스가 콘솔에 직접 쓰도록 서브 생략을 시도 했습니까 ?
S.Lott

5
문제는 출력을 읽고 싶다는 것입니다. 콘솔에 직접 출력되면 어떻게 할 수 있습니까? 또한 사용자가 래핑 된 프로그램의 출력을 보지 않기를 원합니다.
Chris Lieb

그렇다면 왜 "실시간"디스플레이입니까? 유스 케이스를 얻지 못했습니다.
S.Lott

8
shell = True를 사용하지 마십시오. 불필요하게 쉘을 호출합니다. 대신 p = Popen ([ 'svnadmin', 'verify', '/ var / svn / repos / config'], stdout = PIPE, stderr = STDOUT)을 사용하십시오
nosklo

2
@ S.Lott 기본적으로 svnadmin verify는 검증 된 모든 개정에 대한 출력 라인을 인쇄합니다. 과도한 양의 출력을 유발하지 않는 멋진 진행률 표시기를 만들고 싶었습니다. 예를 들어
Chris Lieb

답변:


82

나는 이것을 시도했고 어떤 이유로 코드가있는 동안

for line in p.stdout:
  ...

적극적으로 버퍼를 변형

while True:
  line = p.stdout.readline()
  if not line: break
  ...

하지 않습니다. : 분명히 이것은 알려진 버그 http://bugs.python.org/issue3907 (이 문제는 이제 2018 년 8 월 29로 "닫기"는)


이것은 오래된 파이썬 IO 구현에서 유일한 혼란은 아닙니다. 이것이 Py2.6과 Py3k가 완전히 새로운 IO 라이브러리로 끝나는 이유입니다.
Tim Lin

3
하위 프로세스가 빈 줄을 반환하면이 코드가 중단됩니다. 더 나은 해결책은 while p.poll() is None대신에 사용 while True하고if not line
exhuma를

6
@ exhuma : 잘 작동합니다. readline은 빈 줄에 "\ n"을 반환하며 이는 true로 평가되지 않습니다. 파이프가 닫힐 때 빈 문자열 만 리턴하며, 이는 서브 프로세스가 종료 될 때입니다.
Alice Purcell

1
@Dave 향후 참조를 위해 py2 +에서 utf-8 행을로 인쇄하십시오 print(line.decode('utf-8').rstrip()).
Jonathan Komar

3
또한 프로세스의 출력을 실시간으로 읽으려면 파이썬에게 버퍼링을 원하지 않는다고 알려 주어야합니다. 친애하는 Python은 출력을 직접 제공합니다. 그리고 방법은 다음과 같습니다 PYTHONUNBUFFERED=1. 환경 변수를 설정해야합니다 . 이것은 특히 무한한 출력에 유용합니다
George Pligoropoulos

38
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()

1
@nbro는 아마도 p.stdout.close()불분명 하기 때문일 것입니다 .
anatoly techtonik 2016 년

1
@nbro 아마도 코드가 설명없이 주어 졌을 것입니다 ... : /
Aaron Hall

3
이 b ''는 무엇입니까?
ManuelSchneid3r

29

서브 프로세스 출력을 스트림으로 직접 지정할 수 있습니다. 단순화 된 예 :

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

이것으로 사실 이후에 내용을 얻을 수 .communicate()있습니까? 또는 내용이 부모 stderr / stdout 스트림으로 손실됩니까?
theferrit32

아니요, communicate()반환 된 메소드가 없습니다 CompletedProcess. 또한, capture_output함께 상호 배타적 stdout하고 stderr.
Aidan Feldman

20

당신은 이것을 시도 할 수 있습니다 :

import subprocess
import sys

process = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()

읽기 대신 readline을 사용하면 입력 메시지가 인쇄되지 않는 경우가 있습니다. 인라인 입력이 필요한 명령으로 시도해보십시오.


예, readline ()을 사용하면 인쇄가 중지됩니다 (sys.stdout.flush ()를 호출하더라도)
Mark Ma

3
이것이 무기한 매달리기로되어 있습니까? 초기 하위 프로세스가 완료되면 루프를 편집하기위한 상용구 코드를 포함하는 솔루션을 원합니다. 얼마나 많은 시간을 보더라도 하위 프로세스 등은 내가 일할 수없는 것입니다.
ThorSummoner

1
파이썬에서 ''을 왜 테스트해야합니까?
Greg Bell

2
장기 실행 작업에 가장 적합한 솔루션입니다. 그러나 None이 아니라! = None이 아니어야합니다. ! =를 None과 함께 사용해서는 안됩니다.
Cari

이것으로 stderr도 표시됩니까?
Pieter Vogelaar 2016 년

7

스트리밍 서브 프로세스의 표준 입력과 표준 출력과 asyncio 파이썬에 의한 블로그 포스트 케빈 맥카시의 asyncio으로 작업을 수행하는 방법을 보여줍니다 :

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

이것은 게시 된 코드를 약간 수정하여 작동합니다.
Jeef

안녕 @Jeef 대답을 업데이트 할 수 있도록 수정 사항을 지적 할 수 있습니까?
Pablo

안녕하세요, 저에게 효과적이지만 일부 오류 메시지를 제거하고 다음 대신 import nest_asyncio; nest_asyncio.apply()쉘 명령을 사용 하려면 다음을 추가해야했습니다 . 건배! process = await create_subprocess_shell(*command, stdout=PIPE, stderr=PIPE, shell=True)process = await create_subprocess_exec(...)
user319436

4

실시간 출력 문제 해결 : 파이썬에서 비슷한 문제가 발생하여 c 프로그램에서 실시간 출력을 캡처합니다. " fflush (stdout) ;"를 추가했습니다 . 내 C 코드에서. 그것은 나를 위해 일했다. 다음은 코드 스니핑입니다.

《C 프로그램》

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

<< 파이썬 프로그램 >>

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)

<< 출력 >> 인쇄 : 카운트 1 인쇄 : 카운트 2 인쇄 : 카운트 3

도움이 되길 바랍니다.

~ 사이 람


1
이것이 실제로 도움이 된 유일한 것입니다. flush(stdout)C ++에서 동일한 코드 ( )를 사용했습니다 . 감사!
Gerhard Hagerer

하위 프로세스로 다른 파이썬 스크립트를 호출하는 파이썬 스크립트와 동일한 문제가있었습니다. 하위 프로세스 인쇄에서 "플러시"가 필요했습니다 (파이썬 3에서 print ( "hello", flush = True)). 또한, 거기에 많은 예제가 여전히 (2020) python 2입니다. 이것은 python 3이므로 +1
smajtkst

3

나는 다시 같은 문제에 부딪쳤다. 내 솔루션은 read메서드에 대한 반복을 제거하는 것이 었습니다. 하위 프로세스가 실행을 마치지 않아도 즉시 반환됩니다.


3

유스 케이스에 따라 서브 프로세스 자체에서 버퍼링을 사용하지 않을 수도 있습니다.

하위 프로세스가 Python 프로세스 인 경우 호출 전에이를 수행 할 수 있습니다.

os.environ["PYTHONUNBUFFERED"] = "1"

또는 대안으로 이것을 env인수에 전달하십시오.Popen .

그렇지 않으면 Linux / Unix에있는 경우 stdbuf도구를 사용할 수 있습니다 . 예 :

cmd = ["stdbuf", "-oL"] + cmd

참조 여기 에 대한stdbuf 다른 옵션을 제공합니다.

( 동일한 답변 은 여기 를 참조하십시오 .)


2

이 솔루션을 사용하여 하위 프로세스에서 실시간 출력을 얻었습니다. 이 루프는 프로세스가 완료 되 자마자 break 문이나 무한 루프가 필요하지 않게되면 중지됩니다.

sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()

5
stdout 버퍼가 비어 있지 않고 루프를 종료 할 수 있습니까?
jayjay

나는 완성시 멈추지 않은 적절한 답변을 많이 찾았습니다! 나는 추가하여 솔루션이 발견 if out=='': breakout = sub_process...
조난 신호

2

이 "플러그 앤 플레이"기능을 여기서 찾으 십시오 . 매력처럼 일했다!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)

1
stderr=subprocess.STDOUT실제로 추가 하면 스트리밍 데이터를 캡처하는 데 많은 도움이됩니다. 나는 그것을 높이고있다.
khan

1
여기에 주요 쇠고기는 허용 된 답변
tripleee

2

서브 프로세스의 출력에서 ​​각 바이트에 대해 반복자를 사용할 수 있습니다. 서브 프로세스에서 인라인 업데이트 ( '\ r'로 끝나는 라인이 이전 출력 라인을 덮어 씁니다)를 허용합니다.

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")

2

Python 3.x에서는 출력이 문자열 대신 바이트 배열이므로 프로세스가 중단 될 수 있습니다. 문자열로 디코딩해야합니다.

Python 3.6부터 Popen Constructor 의 매개 변수 encoding를 사용하여이를 수행 할 수 있습니다 . 완전한 예 :

process = subprocess.Popen(
    'my_command',
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True,
    encoding='utf-8',
    errors='replace'
)

while True:
    realtime_output = process.stdout.readline()

    if realtime_output == '' and process.poll() is not None:
        break

    if realtime_output:
        print(realtime_output.strip(), flush=True)

참고이 코드가 리디렉션 stderrstdout핸들 출력 오류 .


1

비 차단 readlines와 함께 pexpect [ http://www.noah.org/wiki/Pexpect ]를 사용하면 이 문제가 해결됩니다. 파이프가 버퍼링되어 파이프에 의해 앱의 출력이 버퍼링되기 때문에 버퍼가 채워지거나 프로세스가 종료 될 때까지 해당 출력에 도달 할 수 없습니다.


0

완벽한 솔루션 :

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()

1
당신이 사용하고 있기 때문에 universal_newlines=True상의 Popen()옵션의 전체 지점의 그 - 전화, 당신은 아마 너무, 그들의 자신의 처리를 넣을 필요가 없습니다.
martineau

1
복잡하지 않은 것 같습니다. 버퍼링 문제는 해결되지 않습니다. 내 답변의 링크를 참조하십시오 .
jfs

이것이 실시간으로 rsync progress 출력을 얻을 수있는 유일한 방법입니다 (--outbuf = L)! 감사합니다
Mohammadhzp

0

이것은 항상 이것을 위해 사용하는 기본 골격입니다. 시간 초과를 쉽게 구현할 수 있으며 불가피한 중단 프로세스를 처리 할 수 ​​있습니다.

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()

0

(이 솔루션은 Python 2.7.15에서 테스트되었습니다.
) 각 줄 읽기 / 쓰기 후에 sys.stdout.flush ()를 실행하면됩니다.

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()

0

python 3.x 또는 pthon 2.x를 제안하는 답변은 거의 없으며 아래 코드는 둘 다 작동합니다.

 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,)
    stdout = []
    while True:
        line = p.stdout.readline()
        if not isinstance(line, (str)):
            line = line.decode('utf-8')
        stdout.append(line)
        print (line)
        if (line == '' and p.poll() != None):
            break
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.