서브 프로세스 stdout을 한 줄씩 읽으십시오


235

내 파이썬 스크립트는 하위 프로세스를 사용하여 매우 시끄러운 리눅스 유틸리티를 호출합니다. 모든 출력을 로그 파일에 저장하고 일부를 사용자에게 보여주고 싶습니다. 나는 다음과 같이 작동 할 것이라고 생각했지만 유틸리티가 상당한 양의 출력을 생성 할 때까지 응용 프로그램에 출력이 표시되지 않습니다.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

필자가 실제로 원하는 동작은 필터 스크립트가 서브 프로세스로부터 수신 될 때 각 라인을 인쇄하는 것입니다. tee파이썬 코드와 비슷 하지만 파이썬 코드와 비슷 합니다.

내가 무엇을 놓치고 있습니까? 이것도 가능합니까?


최신 정보:

a sys.stdout.flush()가 fake_utility.py에 추가되면 코드는 python 3.1에서 원하는 동작을 갖습니다. 파이썬 2.6을 사용하고 있습니다. 사용 proc.stdout.xreadlines()은 py3k와 동일하게 작동 한다고 생각 하지만 그렇지 않습니다.


업데이트 2 :

다음은 최소 작업 코드입니다.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()

4
print line,대신에 사용할 수 있습니다 print line.rstrip()(주 : 끝에 쉼표).
jfs


2
업데이트 2에서는 python 3.0+에서는 작동하지만 이전 print 문을 사용하므로 python 3.0+에서는 작동하지 않습니다.
Rooky

나를 위해 여기에 나열된 일 답변 중에 있지만, stackoverflow.com/questions/5411780/...은 하지 않았다!
박스

답변:


179

파이썬으로 마지막으로 작업 한 지 오랜 시간이 지났지 만 문제는 문 for line in proc.stdout을 반복하기 전에 전체 입력을 읽는 문에 있다고 생각 합니다. 해결책은 readline()대신 사용하는 것입니다.

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

물론 하위 프로세스의 버퍼링을 처리해야합니다.

참고 : 문서에 따르면 반복 미리보기가있는 솔루션은 readline()미리 읽기 버퍼를 제외하고 를 사용하는 것과 동일해야 하지만 제안 된 변경으로 인해 다른 결과가 나왔습니다 (Windows XP의 Python 2.5).


11
에 대한 file.readline()비교는 for line in file참조 bugs.python.org/issue3907을 , (사용이 Python3에서 작동 짧은 io.open()2.6+ 파이썬)
JFS

5
PEP 8의 "프로그래밍 권장 사항"( python.org/dev/peps/pep-0008 ) 에 따라 EOF에 대한 더 많은 pythonic 테스트는 'not not line :'입니다.
Jason Mock

14
@naxa : 파이프의 경우 : for line in iter(proc.stdout.readline, ''):.
jfs

3
@ Jan-PhilipGehrcke : 예. 1. for line in proc.stdoutPython 3에서 사용할 수 있습니다 (읽기 미리 버그가 없음) 2. '' != b''Python 3에서-코드를 맹목적으로 복사하여 붙여 넣지 마십시오. 코드의 기능과 작동 방식을 생각하십시오.
jfs

2
@ JFSebastian : 물론 iter(f.readline, b'')해결책은 다소 분명합니다 (관심이 있다면 Python 2에서도 작동합니다). 내 의견의 요점은 귀하의 솔루션을 비난하는 것이 아니라 (죄송하지만, 지금도 읽습니다!),이 경우 상당히 심각한 증상의 정도를 설명하는 것이 었습니다 (대부분의 Py2 / 3 문제는 예외를 초래하지만, 여기서 잘 동작하는 루프는 끝이없는 것으로 바뀌었고 가비지 수집은 새로 생성 된 객체의 홍수와 싸우는 데 어려움을 겪으며, 장기간 및 큰 진폭으로 메모리 사용 진동이 발생합니다).
Dr. Jan-Philip Gehrcke

45

파티에 늦었지만 여기에서 가장 간단한 해결책이 무엇인지 생각하지 않는 것에 놀랐습니다.

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(파이썬 3이 필요합니다.)


25
이 답변을 사용하고 싶지만 AttributeError: 'file' object has no attribute 'readable' py2.7
Dan Garthwaite

3
python 3와 함께 작동
matanster

이 코드는 여러 가지 이유로 py3 / py3 호환성 및 ValueError : 실제 파일에 대한 I / O 작업
sorin

3
@sorin이 둘 중 어느 것도 "유효하지 않습니다". 여전히 Python 2를 지원해야하는 라이브러리를 작성하는 경우이 코드를 사용하지 마십시오. 그러나 많은 사람들은 10 년 전보다 최근에 출시 된 소프트웨어를 사용할 수있는 사치를 가지고 있습니다. 닫힌 파일을 읽으려고하면 사용 여부에 관계없이 해당 예외가 발생합니다 TextIOWrapper. 단순히 예외를 처리 할 수 ​​있습니다.
jbg

1
당신은 파티에 늦었을지 모르지만 당신은 정답은 최신 버전의 Python, ty
Dusan Gligoric

20

실제로 반복자를 정렬하면 버퍼링이 문제가 될 수 있습니다. 하위 프로세스의 파이썬에게 출력을 버퍼링하지 않도록 지시 할 수 있습니다.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

된다

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

파이썬 내에서 파이썬을 호출 할 때 이것이 필요했습니다.


14

이러한 추가 매개 변수를 다음으로 전달하려고합니다 subprocess.Popen.

bufsize=1, universal_newlines=True

그런 다음 예제와 같이 반복 할 수 있습니다. (파이썬 3.5에서 테스트)


2
@nicoulaj subprocess32 패키지를 사용하는 경우 작동합니다.
Quantum7

4

둘다 순회를 가능하게하는 기능 stdoutstderr라인으로 라인을 동시에, 실시간

경우에 당신은 모두 출력 스트림을 얻을 필요 stdout하고 stderr동시에 다음과 같은 기능을 사용할 수 있습니다.

이 함수는 큐를 사용하여 두 Popen 파이프를 단일 반복기로 병합합니다.

여기에 함수를 만듭니다 read_popen_pipes():

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()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() 사용:

import subprocess as sp


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):

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code

2

루프없이 줄을 읽을 수도 있습니다. python3.6에서 작동합니다.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()

1
: 또는 문자열로 변환list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv

1

내가 python3에 이것을 시도하고, 작업 소스

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()

1

Rômulo의 답변을 다음과 같이 수정하면 Python 2 및 3 (2.7.12 및 3.6.1)에서 작동합니다.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break

0

이것이 하위 프로세스 모듈에 추가되었을 때 Dunno이지만 Python 3에서는 다음을 사용하는 것이 좋습니다 proc.stdout.splitlines().

for line in proc.stdout.splitlines():
   print "stdout:", line
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.