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

유체 역학 코드의 드라이버로 파이썬 스크립트를 사용하고 있습니다. 시뮬레이션을 실행할 때가되면 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')를 실행하십시오 .



답변

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둘 중 하나의 문자가 파이프에 기록되거나 라인이 각각 파이프에 기록 될 때까지 기능을 차단합니다.


답변

요약 (또는 “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보다 큰 스트림을 생성합니다 .)


답변

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

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


답변

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


답변

해결 방법 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()


답변

우수하지만 “무거운”솔루션은 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


답변

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

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끝에) 반환하고 실제로 끝에 있으면 비어 있습니다.

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