subprocess.communicate ()에서 스트리밍 입력 읽기


84

subprocess.communicate()약 1 분 동안 실행되는 프로세스에서 표준 출력을 읽기 위해 Python을 사용 하고 있습니다.

해당 프로세스의 각 줄을 stdout스트리밍 방식으로 인쇄하여 생성 된 출력을 볼 수 있지만 계속하기 전에 프로세스가 종료되는 것을 차단하려면 어떻게해야합니까?

subprocess.communicate() 한 번에 모든 출력을 제공하는 것처럼 보입니다.


답변:


44

내가 생각하는주의 사항 (아래) JF 세바스찬의 방법은 더 좋다.


다음은 오류를 확인하지 않는 간단한 예입니다.

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

경우 ls모든 데이터를 읽은 전에 끝이 너무 빨리, 그 동안 루프가 종료 될 수 있습니다.

다음과 같이 stdout에서 나머지를 잡을 수 있습니다.

output = proc.communicate()[0]
print output,

1
이 체계는 파이썬 문서가 언급하는 버퍼 차단 문제의 희생양이됩니까?
Heinrich Schmetterling

@Heinrich, 버퍼 차단 문제는 내가 잘 이해하는 것이 아닙니다. 나는이 문제가 while 루프 내에서 stdout (및 stderr?)에서 읽지 않는 경우에만 발생한다고 믿습니다. 그래서 위의 코드는 괜찮다고 생각하지만 확실히 말할 수는 없습니다.
unutbu

1
이것은 실제로 블로킹 문제로 고통받습니다. 몇 년 전에 proc이 끝났어도 개행을 얻을 때까지 readline이 차단되는 문제에 끝이 없었습니다. 나는 해결책을 기억하지 못하지만 작업자 스레드에서 읽기를 수행하고 루프 while proc.poll() is None: time.sleep(0)또는 그 효과와 관련이 있다고 생각합니다. 기본적으로 출력 줄 바꿈이 프로세스가 수행하는 마지막 작업인지 확인하거나 (통역사에게 다시 반복 할 시간을 줄 수 없기 때문에) "멋진"작업을 수행해야합니다.
dash-tom-bang

@Heinrich : Alex Martelli가 교착 상태를 피하는 방법에 대해 다음과 같이 씁니다. stackoverflow.com/questions/1445627/…
unutbu

6
버퍼 블로킹은 때때로 들리는 것보다 더 간단합니다. 자식이 종료되기를 기다리는 부모 블록 + 부모가 읽기를 기다리고있는 자식 블록이 가득 찬 통신 파이프의 일부 공간 = 교착 상태입니다. 그렇게 간단합니다. 파이프가 작을수록 발생할 가능성이 높습니다.
MarcH 2013 년

160

하위 프로세스가 stdout 버퍼를 플러시하자마자 하위 프로세스의 출력을 한 줄씩 가져 오려면 :

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()Python 2의 미리 읽기 버그 를 해결 하기 위해 작성되는 즉시 행을 읽는 데 사용됩니다 .

하위 프로세스의 stdout이 비대화 형 모드에서 라인 버퍼링 대신 블록 버퍼링을 사용하는 경우 (이로 인해 자식의 버퍼가 가득 차거나 자식이 명시 적으로 플러시 할 때까지 출력이 지연됨) 다음을 사용하여 버퍼링되지 않은 출력을 강제로 시도 할 수 있습니다. pexpect, pty모듈 또는 unbuffer, stdbuf, script유틸리티 , 참조 왜 그냥 파이프 사용 (는 popen을 ()) : Q는?


다음은 Python 3 코드입니다.

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

참고 : 하위 프로세스의 바이트 문자열을 그대로 출력하는 Python 2와 달리; Python 3은 텍스트 모드를 사용합니다 (cmd의 출력은 locale.getpreferredencoding(False)인코딩을 사용하여 디코딩 됨 ).


b ''는 무엇을 의미합니까?
Aaron

4
b''bytesPython 2.7 및 Python 3 의 리터럴입니다.
jfs 2014

2
@JinghaoShi : 하위 프로세스에 (를 사용하여 ) 쓰기 (사용 ) bufsize=1하면 차이를 만들 수 있습니다. 읽기만한다면 성능에만 차이가 있다고 말했듯이 그렇지 않은 경우이를 보여주는 최소한의 완전한 코드 예제를 제공 할 수 있습니까? p.stdinpexpect
jfs

1
@ealeon : 네. stderr을 stdout에 병합하지 않는 한 stdout / stderr을 동시에 읽을 수있는 기술이 필요합니다 (에 전달 stderr=subprocess.STDOUT하여 Popen()). 여기에 링크 된 스레딩 또는 asyncio 솔루션 도 참조하십시오 .
jfs

2
@saulspatz stdout=PIPE가 출력을 캡처하지 않으면 (여전히 화면에 표시됨) 프로그램이 stderr로 인쇄하거나 대신 터미널에 직접 인쇄 할 수 있습니다. stdout & stderr을 병합하려면 전달 stderr=subprocess.STDOUT하십시오 (이전 설명 참조). tty에 직접 인쇄 된 출력을 캡처하려면 pexpect, pty 솔루션을 사용할있습니다. . 다음은 더 복잡한 코드 예제 입니다.
jfs

6

스트리밍 방식으로 프로세스에서 출력을 수집하는 가장 간단한 방법은 다음과 같습니다.

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

readline()또는 read()읽을 수있는 것도이 (가없는 경우 그렇지 않은 경우는 차단 - 기능은 프로세스가 종료 된 후, EOF에 빈 문자열을 반환해야합니다 readline(), 줄 바꿈을 포함하므로 빈 줄에, 그것은 반환 "\ n"). 이렇게하면 communicate()루프 후 어색한 최종 호출 이 필요하지 않습니다 .

read()이 매우 긴 파일에서는 최대 메모리 사용량을 줄이는 것이 바람직 할 수 있습니다. 전달 된 수는 임의적이지만이를 제외하면 전체 파이프 출력을 한 번에 읽는 것이 바람직하지 않을 수 있습니다.


4
data = proc.stdout.read()모든 데이터를 읽을 때까지 차단 됩니다. os.read(fd, maxsize)(데이터를 사용할 수있는 즉시) 이전에 반환 할 수 있는 것과 혼동 될 수 있습니다 .
jfs 2013-08-22

당신 말이 맞아요. 그러나 적절한 수의 바이트가 인수로 전달되면 read()제대로 작동 readline()하며 최대 줄 길이가 합리적이면 마찬가지로 잘 작동합니다. 그에 따라 내 대답을 업데이트했습니다.
D Coetzee


3

단순히 실시간으로 출력을 전달하려는 경우 다음보다 더 간단하게하기가 어렵습니다.

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

subprocess.check_call ()에 대한 문서를 참조하십시오 .

출력을 처리해야하는 경우에는 루프를 실행하십시오. 그러나 그렇지 않다면 간단하게 유지하십시오.

편집 : JF Sebastian 은 stdout 및 stderr 매개 변수의 기본값이 sys.stdout 및 sys.stderr로 전달되고 sys.stdout 및 sys.stderr이 교체되면 실패한다고 지적합니다 (예 : 테스트).


실제 fileno ()가없는 파일 류 객체로 대체 sys.stdout되거나 sys.stderr대체 되면 작동하지 않습니다 . 경우 sys.stdout, sys.stderr다음 대체하지 않습니다 심지어는 간단하다 : subprocess.check_call(args).
jfs

감사! 나는 sys.stdout / stderr을 대체하는 모호함을 깨달았지만, 인수를 생략하면 stdout과 stderr을 올바른 위치로 전달한다는 사실을 결코 깨닫지 못했습니다. 내가 좋아하는 call()이상 check_call()내가 원하는하지 않는 CalledProcessError.
Nate

python -mthis: "오류는 자동으로 전달되지 않아야합니다. 명시 적으로 침묵하지 않는 한." 왜 '즉 예제 코드 를 선호한다 check_call()이상 call().
jfs

헤. 내가 끝내는 많은 프로그램 call()은 끔찍하기 때문에 오류가 아닌 조건에서 0이 아닌 오류 코드를 반환합니다. 따라서 우리의 경우 0이 아닌 오류 코드는 실제로 오류가 아닙니다.
Nate

예. grep오류가 없어도 0이 아닌 종료 상태를 반환 할 수 있는 프로그램 이 있습니다. 예외입니다. 기본적으로 0 종료 상태는 성공을 나타냅니다.
jfs 2015 년

1
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))

1
사람들이 더 잘 이해할 수 있도록 솔루션이 수행하는 작업을 설명하는 것은 항상 좋은
DaFois

2
shlex.split(myCommand)대신 사용을 고려해야 myCommand.split()합니다. 인용 된 인수의 공백도 존중합니다.
UtahJarhead

0

몇 가지 작은 변경 사항으로 다른 python3 솔루션 추가 :

  1. 쉘 프로세스의 종료 코드를 잡을 수 있습니다 ( with구성 을 사용하는 동안 종료 코드를 가져올 수 없습니다 ).
  2. 또한 실시간으로 stderr을 파이프합니다.
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.