하위 프로세스 명령의 라이브 출력


186

유체 역학 코드의 드라이버로 파이썬 스크립트를 사용하고 있습니다. 시뮬레이션을 실행할 때가되면 subprocess.Popen코드를 실행하고 stdout 및 stderr에서 출력을 subprocess.PIPE--- 로 수집 한 다음 출력 정보를 인쇄 (및 로그 파일에 저장)하고 오류를 확인할 수 있습니다. . 문제는 코드가 어떻게 진행되고 있는지 전혀 모른다는 것입니다. 명령 줄에서 직접 실행하면 반복 시간, 시간, 다음 시간 단계 등을 출력합니다.

출력을 저장하고 (로깅 및 오류 검사 용) 라이브 스트리밍 출력을 생성하는 방법이 있습니까?

내 코드의 관련 섹션 :

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

원래 나는 배관되었다 run_command통해 tee복사본 로그 파일에 직접 가서 있도록, 여전히 출력이 직접 단말기에 스트림 -하지만 (내 knowlege에) 나는 오류를 저장할 수있는 방법입니다.


편집하다:

임시 솔루션 :

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

그런 다음 다른 터미널에서 tail -f log.txt(st log_file = 'log.txt')를 실행하십시오 .


1
어쩌면 이전 스택 오버플로 질문Popen.poll 에서처럼 사용할 수 있습니다 .
— Paulo Almeida

진행 표시 (예 :)를 표시하는 일부 명령은 git출력이 "tty 장치"(libc를 통해 테스트 됨) 인 경우에만 수행 isatty()합니다. 이 경우 의사 tty를 열어야 할 수도 있습니다.
— torek

@torek (의사) tty는 무엇입니까?
— DilithiumMatrix

2
프로세스가 직렬 포트에서 사용자 인 것처럼 가장 할 수있는 유닉스 계열 시스템의 장치 예를 들어 ssh (서버 측)의 작동 방식입니다. python pty library 및 pexpect 도 참조하십시오 .
— torek

임시 방편 재 : 호출 할 필요가 없습니다 flush, 거기 이다 서브 프로세스가 많은 표준 에러 출력을 생성하면 열려진 파이프에서 읽을 필요. 코멘트 필드에는 이것을 설명하기에 충분한 공간이 없습니다 ...
— torek

답변:


169

read또는 readline함수 에서 반복자를 작성하여 다음 두 가지 방법으로이를 수행 할 수 있습니다.

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):  # replace '' with b'' for Python 3
        sys.stdout.write(c)
        f.write(c)

또는

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):  # replace '' with b'' for Python 3
        sys.stdout.write(line)
        f.write(line)

또는 당신은 만들 수 있습니다 reader및 writer파일을. 에 writer전달하고 Popen에서 읽기reader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

이 방법으로 test.log표준 출력뿐만 아니라 데이터도 쓸 수 있습니다.

파일 접근 방식의 유일한 장점은 코드가 차단되지 않는다는 것입니다. 따라서 그 동안 원하는 모든 작업을 수행 할 수 있으며 원하는 reader경우 비 차단 방식으로 읽을 수 있습니다. 당신이 사용하는 경우 PIPE, read그리고 readline둘 중 하나의 문자가 파이프에 기록되거나 라인이 각각 파이프에 기록 될 때까지 기능을 차단합니다.


1
Ugh :-) 파일에 쓰고, 읽고, 루프에서 잠을 자나요? 파일 읽기를 마치기 전에 프로세스가 종료 될 수도 있습니다.
— Guy Sirton

13
파이썬 3으로, 당신은 필요 iter(process.stdout.readline, b'')(예에 전달 된 감시 ITER는 이후, 진 문자열이 될 필요가있다 b'' != ''.
— 존 멜러

3
이진 스트림의 경우 다음과 같이하십시오.for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
— rrlamichhane

6
@JohnMellor의 답변에 덧붙여 파이썬 3에서는 다음과 같은 수정이 필요했습니다. process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding))
— bergercookie

4
그러나 결과물은 살아 있지 않습니까? 내 경험상, 프로세스 실행이 끝날 때까지 기다렸다가 콘솔에 인쇄합니다. 링크-> stackoverflow.com/questions/30026045/…
— denis631

91

요약 (또는 "tl; dr"버전) : 최대 하나만 있으면 쉽게 수행 할 수 있습니다 subprocess.PIPE. 그렇지 않으면 어렵습니다.

subprocess.Popen그 일을 어떻게하는지에 대해 조금 설명해야 할 때 입니다.

(주의 : 이것은 3.x와 비슷하지만 Python 2.x 용입니다 .Windows 변형에 대해서는 매우 모호합니다. POSIX를 훨씬 잘 이해합니다.)

이 Popen기능은 0 ~ 3 개의 I / O 스트림을 동시에 처리해야합니다. 이러한 표시된다 stdin, stdout그리고, stderr평소와 같이.

다음을 제공 할 수 있습니다.

  • None스트림을 리디렉션하고 싶지 않음을 나타냅니다. 대신 평상시와 같이 상속합니다. POSIX 시스템에서 이것은 최소한 sys.stdout파이썬의 실제 표준 출력 인 파이썬을 사용한다는 의미는 아닙니다 . 마지막 데모를 참조하십시오.
  • int값. 이것은 "raw"파일 디스크립터입니다 (최소한 POSIX). (사이드 노트 : PIPE그리고 STDOUT실제로 int. 내부이야,하지만있다 "불가능"기술자, -1과 -2)
  • 스트림 — 실제로 fileno메소드 가있는 모든 객체 . Popen를 사용하여 해당 스트림에 대한 설명자를 찾은 stream.fileno()다음 int값으로 진행 합니다.
  • subprocess.PIPE, 파이썬이 파이프를 만들어야 함을 나타냅니다.
  • subprocess.STDOUT( stderr만 해당) : Python에게 for와 동일한 설명자를 사용하도록 지시하십시오 stdout. 에 대해 (비 None) 값을 제공 한 경우에만 의미가 stdout있으며, 설정 한 경우 에만 필요 합니다 stdout=subprocess.PIPE. (그렇지 않으면에 제공 한 것과 동일한 인수를 제공 할 수 있습니다 ( stdout예 :) Popen(..., stdout=stream, stderr=stream))

가장 쉬운 경우 (파이프 없음)

아무것도 리디렉션하지 않으면 (세 가지 모두 기본값으로 None두거나 explicit을 명시하십시오 None) Pipe매우 쉽습니다. 하위 프로세스를 분리하고 실행하면됩니다. 당신이 비에 리디렉션 경우 또는 PIPE-an int또는 스트림의 fileno()OS가 모든 작업을 수행으로 - 그것은이 쉬운 아직. 파이썬은 stdin, stdout 및 / 또는 stderr을 제공된 파일 설명자에 연결하여 하위 프로세스를 분리하기 만하면됩니다.

여전히 쉬운 케이스 : 하나의 파이프

하나의 스트림 만 리디렉션해도 Pipe여전히 쉬운 일이 있습니다. 한 번에 하나의 스트림을 선택하고 시청합시다.

당신은 몇 가지를 제공한다고 가정 stdin하지만,하자 stdout및 stderr취소 리디렉션 이동하거나 파일 기술자로 이동합니다. 부모 프로세스로서, 파이썬 프로그램은 단순히 write()파이프로 데이터를 보내는 데 사용 하면됩니다. 이를 직접 수행 할 수 있습니다 (예 :

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

또는 당신의 표준 입력 데이터를 전달할 수 있습니다 proc.communicate()후 않으며, stdin.write위에 표시합니다. 출력이 나오지 않으므로 communicate()다른 실제 작업이 하나만 있습니다. 파이프를 닫습니다. 호출하지 않으면 하위 프로세스가 더 이상 데이터가 전달되지 않음을 알 수 있도록 파이프를 닫으 proc.communicate()려면 호출해야합니다 proc.stdin.close().

당신이 캡처한다고 가정 stdout하지만, 휴가 stdin및 stderr혼자. 다시 말하지만, 더 쉽습니다. proc.stdout.read()더 이상 출력이 없을 때까지 호출하십시오 . 이후 proc.stdout()정상적인 파이썬 I입니다 / O 당신처럼, 그것은 모든 정상적인 구조를 사용할 수 있습니다 스트리밍 :

for line in proc.stdout:

또는 다시 사용할 수 있습니다 proc.communicate(). 간단히 사용할 수 read()있습니다.

캡처 만하려면와 stderr동일하게 작동합니다 stdout.

일이 힘들어지기 전에 한 가지 더 트릭이 있습니다. 캡처 할 가정 stdout, 또한 캡처 stderr하지만, 표준 출력과 같은 파이프 :

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

이 경우 subprocess"속임수"! 글쎄,이 작업을 수행해야하므로 실제로 속임수가 아닙니다 .stdout과 stderr이 부모 (Python) 프로세스로 피드백되는 (단일) 파이프 디스크립터로 지정된 하위 프로세스를 시작하여 하위 프로세스를 시작합니다. 부모 측에는 출력을 읽는 데 필요한 단일 파이프 설명 자만 있습니다. 모든 "stderr"출력 이에 나타나고을 proc.stdout호출 proc.communicate()하면 stderr 결과 (튜플의 두 번째 값)는 None문자열이 아닙니다.

어려운 경우 : 두 개 이상의 파이프

둘 이상의 파이프를 사용하려고 할 때 문제가 발생합니다. 실제로 subprocess코드 자체에는 다음과 같은 비트가 있습니다.

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

그러나 아아, 여기서 우리는 적어도 두 개, 아마도 세 개의 다른 파이프를 만들었으므로 count(None)1 또는 0을 반환합니다. 우리는 어려운 일을해야합니다.

Windows에서 이는 및 에 threading.Thread대한 결과를 누적 하는 데 사용 되며 상위 스레드가 입력 데이터를 전달한 다음 파이프를 닫습니다.self.stdoutself.stderrself.stdin

POSIX에서, poll가능하다면 select출력을 축적하고 표준 입력을 전달하기 위해 사용합니다. 이 모든 것은 (단일) 부모 프로세스 / 스레드에서 실행됩니다.

교착 상태를 피하려면 스레드 또는 폴링 / 선택이 필요합니다. 예를 들어, 우리가 세 개의 스트림을 모두 세 개의 개별 파이프로 리디렉션했다고 가정합니다. 또한 쓰기 프로세스가 일시 중단되기 전에 파이프에 채워질 수있는 데이터의 양에 약간의 제한이 있고, 읽기 프로세스가 다른 쪽 끝에서 파이프를 "정리"할 때까지 기다린다고 가정하십시오. 설명을 위해 작은 제한을 단일 바이트로 설정해 봅시다. (제한이 1 바이트보다 훨씬 크다는 것을 제외하고는 실제로 작동 방식입니다.)

부모 (Python) 프로세스가 여러 바이트를 쓰려고 하면 ( 예 : 'go\n'to proc.stdin) 첫 번째 바이트가 들어가고 두 번째 바이트는 Python 프로세스가 일시 중단되어 하위 프로세스가 첫 번째 바이트를 읽을 때까지 기다리면서 파이프를 비 웁니다.

한편, 서브 프로세스가 친숙한 "Hello! Do n't Panic!"을 인쇄하기로 결정했다고 가정하십시오. 인사. 는 H자사의 표준 출력 파이프로 전환하지만은 e부모가 읽어 기다리고, 일시 중단을 야기 H표준 출력 파이프를 비우는.

이제 우리는 멈췄습니다. 파이썬 프로세스는 잠 들어 "go"라고 말하기를 기다리고 있으며, 서브 프로세스는 잠 들어 있습니다. "Hello! Do n't Panic!"

subprocess.Popen코드 스레딩 또는-선택 / 설문 조사와 함께이 문제를 피할 수 있습니다. 바이트가 파이프를 통과 할 수 있으면 바이트가 이동합니다. 그들이 할 수 없을 때, 전체 프로세스가 아닌 스레드 만이 잠자기해야한다. 또는 선택 / 폴링의 경우, 파이썬 프로세스는 동시에 "쓰기 가능"또는 "사용 가능한 데이터"를 기다리는 동안 프로세스의 표준 입력에 쓴다 공간이있을 때만 데이터가 준비된 경우에만 stdout 및 / 또는 stderr을 읽습니다. proc.communicate()코드 (실제로 _communicate털이 사건을 취급하는 곳)가 반환 모든 표준 입력 데이터를 한 번에 (있는 경우) 전송 된 모든 표준 출력 및 / 또는 표준 오류 데이터가 축적되어있다.

리디렉션에 관계없이 두 개의 다른 파이프 stdout와 stderr두 개의 파이프를 모두 읽으려면 stdin교착 상태도 피해야합니다. 여기서 교착 상태 시나리오는 다릅니다. 하위 프로세스 stderr에서 데이터를 가져 오는 stdout동안 또는 그 반대로 데이터를 오래 쓸 때 발생 하지만 여전히 존재합니다.


데모

나는 리디렉션되지 않은 Python subprocesses가 기본 stdout에 쓰지 않는다는 것을 보여 주겠다고 약속했습니다 sys.stdout. 다음은 몇 가지 코드입니다.

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
    print 'start show1'
    save = sys.stdout
    sys.stdout = StringIO()
    print 'sys.stdout being buffered'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    in_stdout = sys.stdout.getvalue()
    sys.stdout = save
    print 'in buffer:', in_stdout

def show2():
    print 'start show2'
    save = sys.stdout
    sys.stdout = open(os.devnull, 'w')
    print 'after redirect sys.stdout'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    sys.stdout = save

show1()
show2()

실행할 때 :

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

당신이 추가하면 첫 번째 루틴이 실패하지 않습니다 stdout=sys.stdoutA와, StringIO오브젝트가있다 fileno. 두 번째 는로 리디렉션 된 이후 hello추가 한 경우를 생략합니다 .stdout=sys.stdoutsys.stdoutos.devnull

(Python의 파일 디스크립터 -1 을 리디렉션 하면 하위 프로세스 가 해당 리디렉션 을 따릅니다. open(os.devnull, 'w')호출은 fileno()2보다 큰 스트림을 생성합니다 .)


흠. 귀하의 데모는 결국 주장의 반대를 보여줍니다. Python의 stdout을 버퍼로 리디렉션하지만 하위 프로세스 stdout은 여전히 ​​콘솔로 이동합니다. 그게 어떻게 유용합니까? 뭔가 빠졌습니까?
— Guy Sirton

@GuySirton : 데모는 서브 프로세스 stdout (명시 적으로 지시되지 않은 경우 sys.stdout)이 파이썬 프로그램 의 ( ) stdout이 아니라 Python의 stdout으로 간다는 것을 보여줍니다 . 내가 인정하는 것은 ... 이상한 구별입니다. 이것을 표현하는 더 좋은 방법이 있습니까? sys.
— torek

알고있는 것이 좋지만 여기서 sys.stdout을 변경하는 것이 멋지지만 우리가 생각하는 데 도움이되지 않도록 여기에서 하위 프로세스 출력을 캡처하려고합니다. 의사 소통을 잘 관찰하려면 select (), poll 또는 thread와 같은 것을 사용해야합니다.
— Guy Sirton


나는 select () 구현을 추가했습니다
— sivann

20

readline ()과 함께 iter 구문을 사용하는 대신 stdout을 읽기 위해 기본 파일 반복자를 사용할 수도 있습니다.

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)

가장 우아한 답변은 여기!
— Nir

9
이 솔루션은 실시간으로 표시되지 않습니다. 프로세스가 완료 될 때까지 대기하고 모든 출력을 한 번에 표시합니다. Viktor Kerkez의 솔루션에서 "your_command"가 점진적으로 표시되면 "your_command"가 때때로 파이프로 인해 stdout을 플러시하는 한 출력이 점진적으로 따릅니다.
— Eric H.

1
라이브가 아니기 때문에 @Nir.
— melMass

이 솔루션은 기본 설명자를 반복하므로 출력에서 ​​행이 업데이트 될 때만 업데이트됩니다. 문자 기반 업데이트의 경우 Viktor 솔루션에 표시된 것처럼 read () 메서드를 반복해야합니다. 그러나 그것은 나의 유스 케이스에 대한 과잉이었습니다.
— Jughead

11

타사 라이브러리를 사용할 수 있다면 다음과 같은 것을 사용할 수 있습니다 sarge(공개 : 나는 관리자입니다). 이 라이브러리는 서브 프로세스의 출력 스트림에 대한 비 차단 액세스를 허용 subprocess합니다. 모듈을 통해 계층화 됩니다.


sarge, BTW에 대한 훌륭한 작업. 그것은 실제로 OP의 요구 사항을 해결하지만 그 유스 케이스에는 약간 무겁습니다.
— deepelement

도구를 제안하는 경우이 정확한 사례에 대한 사용 예를 보여주십시오.
— Serhiy

4

해결 방법 1 : 실시간으로 stdoutAND 로그stderr

stdout과 stderr을 동시에 실시간 으로 로그 파일에 기록하는 간단한 솔루션입니다 .

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, stdfile):

    with open("mylog.txt", "w") as f:

        while p.poll() is None:
            f.write(stdfile.readline())
            f.flush()

        # Write the rest from the buffer
        f.write(stdfile.read())


with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, p.stdout)
        r2 = pool.submit(log_popen_pipe, p, p.stderr)
        r1.result()
        r2.result()

솔루션 2 : read_popen_pipes()실시간으로 동시에 두 파이프 (stdout / stderr)를 반복 할 수 있는 기능

import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

# The function in use:

with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):
        print(out_line, end='')
        print(err_line, end='')

    return p.poll()

3

우수하지만 "무거운"솔루션은 Twisted를 사용하는 것입니다. 하단을 참조하십시오.

stdout만으로 살려고한다면 그 라인을 따라 무언가가 작동해야합니다.

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(read ()를 사용하면 유용하지 않은 전체 "파일"을 읽으려고 시도합니다. 여기서 실제로 사용할 수있는 것은 파이프에있는 모든 데이터를 읽는 것입니다.)

다음과 같이 스레딩을 사용하여이 방법을 시도 할 수도 있습니다.

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

이제 두 개의 스레드를 사용하여 stderr을 추가 할 수 있습니다.

서브 프로세스 문서는 직접이 파일을 사용하고 사용을 권장 낙담 그러나 주 communicate()(주로 내가 생각 교착 상태에 관심이 위의 문제가되지 않습니다) 그리고 정말 보인다 있도록 같은 솔루션은 약간 무거운지는있는 서브 프로세스 모듈은 아주 최대 아니다 작업 ( http://www.python.org/dev/peps/pep-3145/ 참조 )과 다른 것을 살펴 봐야합니다.

더 복잡한 솔루션은 다음 과 같이 Twisted 를 사용 하는 것입니다. https://twistedmatrix.com/documents/11.1.0/core/howto/process.html

Twisted 를 사용 하여이 작업을 수행하는 방법 은 프로세스를 사용 reactor.spawnprocess()하고 제공 한 ProcessProtocol다음 출력을 비동기 적으로 처리 하는 것 입니다. 꼬인 샘플 Python 코드는 다음과 같습니다. https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py


감사! 난 그냥 @PauloAlmeida의 의견에 따라이 같은 뭔가를 (시도했지만 subprocess.Popen에 내 전화는 차단 - 즉 그것은 ... 반환 일단은 While 루프에 관해서
— DilithiumMatrix

1
그것은 일어나고 있지 않습니다. 그것은 while 루프에 바로 들어가서 read()하위 프로세스가 종료되고 부모 프로세스 EOF가 파이프에서 수신 할 때까지 호출 을 차단합니다 .
— Alp

@ 알프 흥미로운! 그렇습니다.
— DilithiumMatrix

예, 게시하기에 너무 빠릅니다. 실제로 제대로 작동하지 않으며 쉽게 고칠 수 없습니다. 도면 테이블로 돌아갑니다.
— Guy Sirton

1
@zhermes : 따라서 read ()의 문제는 EOF까지 전체 출력을 읽으려고하는데 유용하지 않다는 것입니다. readline ()이 도움이되고 필요한 전부일 수도 있습니다 (실제로 긴 줄도 문제가 될 수 있습니다). 또한 시작하는 프로세스에서 버퍼링을 조심해야합니다.
— Guy Sirton

3

이 모든 대답 외에도 간단한 접근 방식은 다음과 같습니다.

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

읽을 수있는 한 읽기 가능한 스트림을 반복하고 빈 결과가 나오면 중지하십시오.

여기서 핵심은 출력이 readline()있는 한 줄을 ( \n끝에) 반환하고 실제로 끝에 있으면 비어 있습니다.

이것이 누군가를 돕기를 바랍니다.


3

위의 모든 것을 기반으로 약간 수정 된 버전 (python3)을 제안합니다.

  • while loop calling readline (Iter 솔루션이 제안한 iter 솔루션은 영원히 차단되는 것 같습니다-Python 3, Windows 7)
  • 폴링이 반환되지 않은 후 읽은 데이터 처리를 복제 할 필요가 없습니다.None
  • stderr을 stdout에 파이프하여 두 출력 출력을 모두 읽습니다.
  • cmd의 종료 값을 가져 오는 코드를 추가했습니다.

암호:

import subprocess
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here

제 returncode경우에는 그 부분이 중요했습니다.
— 스타 더스트

2

라인 버퍼 출력이 효과가있는 것 같습니다.이 경우 다음과 같은 것이 적합 할 수 있습니다. (주의 사항 : 테스트되지 않았습니다.) 서브 프로세스의 stdout 만 실시간으로 제공합니다. 실시간으로 stderr과 stdout을 모두 사용하려면보다 복잡한 작업을 수행해야합니다 select.

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...

2

로 stdout직접 설정하지 않으 sys.stdout시겠습니까? 또한 로그로 출력해야하는 경우 f의 write 메소드를 대체 할 수 있습니다.

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)

그것은 작동하지 않을 것입니다 : 하위 프로세스 모듈 stdout은 파일 설명자를 전달하고 전달 된 파일 객체 의 파일 설명 자로 설정 합니다. write-method는 결코 호출되지 않을 것입니다 (적어도 그것은 하위 프로세스가 stderr에 대해하는 것입니다. 나는 stdout에 대해 동일하다고 생각합니다).
— t.animal

2

위의 모든 솔루션은 stderr과 stdout 출력 (다중 파이프)을 분리하지 못했거나 OS 파이프 버퍼가 가득 찼을 때 영원히 차단되었습니다. 하위 프로세스의 poll () 매뉴얼). 내가 찾은 신뢰할 수있는 유일한 방법은 select를 통한 것이지만 posix 전용 솔루션입니다.

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()

또 다른 대안은 파이프 당 하나의 스레드를 분리하는 것입니다. 각 스레드는 다른 스레드를 차단하지 않고 파이프에서 I / O를 차단할 수 있습니다. 그러나 이것은 자체적 인 문제를 소개합니다. 모든 방법에는 성가심이 있으며, 가장 성가신 것을 선택하십시오. :-)
— torek

2

이전 답변과 비슷하지만 다음 솔루션은 Python3을 사용하는 Windows에서 나를 위해 실시간으로 인쇄하고 로그인하는 일반적인 방법을 제공합니다 ( getting-realtime-output-using-python ).

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()

2

이 subprocess.communicate방법은 약간 오해의 소지가 있다고 생각합니다 . 실제로는 stdout 및 stderr 에 지정합니다 subprocess.Popen.

아직 읽는 것은 subprocess.PIPE당신이에 제공 할 수 subprocess.Popen의 표준 출력 과 표준 에러 결국 (당신이 사용해야합니다 여러 프로세스 / 스레드를했습니다 특히 OS 파이프 버퍼를 채우고 당신의 응용 프로그램을 교착 것 매개 변수 subprocess).

내가 제안한 해결책은 stdout 과 stderr 에 파일 을 제공 하고 교착 상태에서 읽는 대신 파일의 내용을 읽는 것입니다 PIPE. 이 파일은 파일을 tempfile.NamedTemporaryFile()쓰는 동안 읽을 수 있도록 액세스 할 수 있습니다 subprocess.communicate.

다음은 샘플 사용법입니다.

        try:
            with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

그리고 이것은 내가하는 일을 설명하기 위해 제공 할 수있는만큼 많은 주석과 함께 사용할 준비 가 된 소스 코드입니다 .

python 2를 사용하는 경우 먼저 pypi에서 최신 버전의 subprocess32 패키지를 설치하십시오 .


import os
import sys
import threading
import time
import tempfile
import logging

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess

logger = logging.getLogger(__name__)


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            # while process is alive, keep reading data
            while not self._process_done:
                out = stdout.readline()
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

            # this is a hack: terraform seems to write to buffer after process has finished
            out = stdout.read()
            if out:
                yield out

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode



1

여기 내 프로젝트 중 하나에서 사용중인 클래스가 있습니다. 서브 프로세스의 출력을 로그로 경로 재 지정합니다. 처음에는 단순히 쓰기 방법을 덮어 쓰려고했지만 하위 프로세스가 호출하지 않기 때문에 작동하지 않습니다 (리디렉션은 파일 설명자 수준에서 발생합니다). 그래서 하위 프로세스 모듈에서 수행되는 방식과 비슷한 내 파이프를 사용하고 있습니다. 이는 모든 로깅 / 인쇄 로직을 어댑터에 캡슐화하는 장점이 있으며 로거의 인스턴스를 다음으로 간단히 전달할 수 있습니다 Popen.subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

로깅이 필요하지 않고 단순히 사용하려는 print()경우 코드의 많은 부분을 제거하고 클래스를 더 짧게 유지할 수 있습니다. 당신은 또한하여 확장 할 수 __enter__및 __exit__방법 전화 finished에서 __exit__쉽게 문맥으로 사용할 수 있도록.


1

파이썬 솔루션 중 어느 것도 나를 위해 일하지 않았습니다. 그 proc.stdout.read()와 비슷한 것이 영원히 차단 될 수 있음이 밝혀졌습니다 .

따라서 다음 tee과 같이 사용 합니다.

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash')

이 솔루션은 이미 사용중인 경우 편리합니다 shell=True.

${PIPESTATUS}전체 명령 체인의 성공 상태를 캡처합니다 (Bash에서만 사용 가능). 를 생략하면 절대 실패하지 않으므로 && exit ${PIPESTATUS}항상 0을 반환 tee합니다.

unbuffer"파이프 버퍼"가 채워질 때까지 너무 오래 기다리지 않고 각 라인을 터미널에 즉시 인쇄하는 데 필요할 수 있습니다. 그러나 버퍼 해제는 assert (SIG Abort)의 종료 상태를 삼 킵니다.

2>&1 또한 stderror를 파일에 기록합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.