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


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 librarypexpect 도 참조하십시오 .
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)

또는 당신은 만들 수 있습니다 readerwriter파일을. 에 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하지만,하자 stdoutstderr취소 리디렉션 이동하거나 파일 기술자로 이동합니다. 부모 프로세스로서, 파이썬 프로그램은 단순히 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하지만, 휴가 stdinstderr혼자. 다시 말하지만, 더 쉽습니다. 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털이 사건을 취급하는 곳)가 반환 모든 표준 입력 데이터를 한 번에 (있는 경우) 전송 된 모든 표준 출력 및 / 또는 표준 오류 데이터가 축적되어있다.

리디렉션에 관계없이 두 개의 다른 파이프 stdoutstderr두 개의 파이프를 모두 읽으려면 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방법은 약간 오해의 소지가 있다고 생각합니다 . 실제로는 stdoutstderr 에 지정합니다 subprocess.Popen.

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

내가 제안한 해결책은 stdoutstderr 에 파일 을 제공 하고 교착 상태에서 읽는 대신 파일의 내용을 읽는 것입니다 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.