하위 프로세스에서 실시간으로 stdout 포착


87

subprocess.Popen()Windows에서 rsync.exe 를 만들고 Python에서 stdout을 인쇄하고 싶습니다 .

내 코드는 작동하지만 파일 전송이 완료 될 때까지 진행 상황을 파악하지 못합니다! 각 파일의 진행 상황을 실시간으로 인쇄하고 싶습니다.

IO를 처리하는 것이 더 나을 것이라고 들었으므로 이제 Python 3.1을 사용하십시오.

import subprocess, time, os, sys

cmd = "rsync.exe -vaz -P source/ dest/"
p, line = True, 'start'


p = subprocess.Popen(cmd,
                     shell=True,
                     bufsize=64,
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()


1
(Google에서 왔습니까?) PIPE의 버퍼 중 하나가 채워지고 읽지 않으면 모든 PIPE가 교착 상태가됩니다. 예 : stderr이 채워질 때 stdout 교착 상태. 읽을 의도가없는 PIPE는 절대 통과하지 마십시오.
— Nasser Al-Wohaibi

누군가 stdout을 subprocess.PIPE 대신 sys.stdout으로 설정할 수없는 이유를 설명 할 수 있습니까?
— Mike

답변:


97

일부에 대한 엄지 손가락의 규칙 subprocess.

  • 사용 하지 마십시오shell=True . 프로그램을 호출하기 위해 불필요하게 추가 쉘 프로세스를 호출합니다.
  • 프로세스를 호출 할 때 인수는 목록으로 전달됩니다. sys.argv파이썬에서는 목록이고 argvC에서도 마찬가지입니다. 따라서 문자열이 아닌 하위 프로세스를 호출 하기 위해 목록 을 전달 Popen합니다.
  • 리디렉션하지 마십시오 stderrA와 PIPE당신이 그것을 읽을하지 않을 때.
  • 글을 쓰지 않을 stdin때 리디렉션하지 마십시오 .

예:

import subprocess, time, os, sys
cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"]

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

for line in iter(p.stdout.readline, b''):
    print(">>> " + line.rstrip())

즉, rsync가 터미널 대신 파이프에 연결되어 있음을 감지하면 출력을 버퍼링 할 가능성이 있습니다. 이것이 기본 동작입니다. 파이프에 연결될 때 프로그램은 실시간 결과를 위해 명시 적으로 stdout을 플러시해야합니다. 그렇지 않으면 표준 C 라이브러리가 버퍼링됩니다.

이를 테스트하려면 대신 다음을 실행하십시오.

cmd = [sys.executable, 'test_out.py']

test_out.py다음 내용 으로 파일을 만듭니다 .

import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")

해당 하위 프로세스를 실행하면 "Hello"가 표시되고 "World"를 제공하기 전에 10 초 동안 기다려야합니다. 즉, 위의하지와 파이썬 코드를 발생하는 경우 rsync, 그 수단 rsync은 운이 그래서 자체가 출력을 버퍼링한다.

해결책은 pty같은 것을 사용하여에 직접 연결 하는 것 pexpect입니다.


12
shell=False특히 사용자가 입력 한 데이터에서 명령 줄을 구성 할 때 옳습니다. 그러나 그럼에도 불구하고 shell=True신뢰할 수있는 소스 (예 : 스크립트에 하드 코딩 된)에서 전체 명령 줄을 가져올 때도 유용합니다.
— Denis Otkidach 09 년

10
@Denis Otkidach : 나는 그것이 shell=True. 생각해보십시오. OS에서 메모리 할당, 디스크 사용량, 프로세서 스케줄링과 관련된 다른 프로세스를 호출 하여 문자열 을 분할하는 것입니다 ! 그리고 하나는 당신이 가입했습니다! 파이썬으로 분할 할 수 있지만 어쨌든 각 매개 변수를 개별적으로 작성하는 것이 더 쉽습니다. 또한 수단 목록을 사용하여 당신은 특별한 쉘 문자를 이스케이프 할 필요가 없습니다 : 공백, ;, >, <, &... 귀하의 매개 변수가 그 문자를 포함 할 수 있습니다 당신은 걱정할 필요가 없습니다! shell=True쉘 전용 명령을 실행하지 않는 한 실제로을 사용하는 이유를 알 수 없습니다 .
— nosklo

nosklo, 다음과 같아야합니다. p = subprocess.Popen (cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
— Senthil Kumaran

1
@mathtick : 왜 이러한 작업을 별도의 프로세스로 수행하는지 모르겠습니다 csv. 모듈 을 사용하여 파일 내용을 자르고 Python에서 첫 번째 필드를 쉽게 추출 할 수 있습니다 . 그러나 예를 들어 파이썬의 파이프 라인은 다음과 같습니다. p = Popen(['cut', '-f1'], stdin=open('longfile.tab'), stdout=PIPE) ; p2 = Popen(['head', '-100'], stdin=p.stdout, stdout=PIPE) ; result, stderr = p2.communicate() ; print result이제 셸이 관련되지 않았으므로 이스케이프 할 필요없이 긴 파일 이름과 셸 특수 문자로 작업 할 수 있습니다. 또한 프로세스가 하나 적기 때문에 훨씬 빠릅니다.
— nosklo

11
Python 2 for line in iter(p.stdout.readline, b'')대신 사용 하지 for line in p.stdout않으면 소스 프로세스가 출력을 버퍼링하지 않더라도 줄을 실시간으로 읽지 않습니다.
— jfs

41

나는 이것이 오래된 주제라는 것을 알고 있지만 지금 해결책이 있습니다. --outbuf = L 옵션을 사용하여 rsync를 호출합니다. 예:

cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest']
p = subprocess.Popen(cmd,
                     stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, b''):
    print '>>> {}'.format(line.rstrip())

3
이것은 작동하며 향후 독자가 위의 모든 대화 상자를 스크롤하는 것을 방지하기 위해 업 보팅되어야합니다.
— VectorVictor 2016

1
@VectorVictor 무슨 일이 일어나고 있는지, 왜 일어나고 있는지 설명하지 않습니다. 다음이 될 때까지 프로그램이 작동 할 수 있습니다. 1. preexec_fn=os.setpgrp프로그램이 상위 스크립트를 유지하도록 추가 합니다. 2. 프로세스의 파이프에서 읽기를 건너 뜁니다. 3. 프로세스가 많은 데이터를 출력하여 파이프를 채 웁니다. 4. 몇 시간 동안 갇혀 있습니다. , 실행중인 프로그램이 임의의 시간 후에 종료되는 이유를 알아 내려고합니다 . @nosklo의 답변은 저에게 많은 도움이되었습니다.
— danuker

15

Linux에서는 버퍼링을 제거하는 것과 동일한 문제가있었습니다. 마지막으로 "stdbuf -o0"(또는 예상에서 버퍼링 해제)을 사용하여 PIPE 버퍼링을 제거했습니다.

proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout

그런 다음 stdout에서 select.select를 사용할 수 있습니다.

참조 /unix/25372/


2
Python에서 C 코드 stdout을 가져 오려는 모든 사람을 위해이 솔루션이 나를 위해 일한 유일한 솔루션임을 확인할 수 있습니다. 명확하게 말하면 Popen의 기존 명령 목록에 'stdbuf', '-o0'을 추가하는 것에 대해 이야기하고 있습니다.
— Reckless

감사합니다! C ++ 앱을 생성하고 특정 로그 문을 내보내는 지 확인하는 여러 pytest / pytest-bdd 테스트에서 정말 유용한 stdbuf -o0것으로 입증되었습니다 . 이없는 경우 이러한 테스트는 C ++ 프로그램에서 (버퍼링 된) 출력을 가져 오는 데 7 초가 필요했습니다. 이제 그들은 거의 즉시 실행됩니다! stdbuf -o0
— evadeflow

11

사용 사례에 따라 하위 프로세스 자체에서 버퍼링을 비활성화 할 수도 있습니다.

하위 프로세스가 Python 프로세스 인 경우 호출 전에 다음을 수행 할 수 있습니다.

os.environ["PYTHONUNBUFFERED"] = "1"

또는 env인수에 이것을 전달하십시오 Popen.

그렇지 않고 Linux / Unix를 사용하는 경우 stdbuf도구를 사용할 수 있습니다 . 예 :

cmd = ["stdbuf", "-oL"] + cmd

또는 기타 옵션 에 대해서는 여기 를 참조하십시오 stdbuf.


1
당신은 PYTHONUNBUFFERED = 1, 내 하루 감사를 저장
— diewland

9
for line in p.stdout:
  ...

다음 줄 바꿈까지 항상 차단됩니다.

"실시간"동작의 경우 다음과 같이해야합니다.

while True:
  inchar = p.stdout.read(1)
  if inchar: #neither empty string nor None
    print(str(inchar), end='') #or end=None to flush immediately
  else:
    print('') #flush for implicit line-buffering
    break

while 루프는 자식 프로세스가 표준 출력을 닫거나 종료 할 때 남겨집니다. read()/read(-1)자식 프로세스가 표준 출력을 닫거나 종료 될 때까지 차단됩니다.


1
inchar대신 None사용 if not inchar:하지 않습니다 ( read()EOF에 빈 문자열 반환). btw, for line in p.stdoutPython 2에서 실시간으로 전체 줄을 인쇄하지 않는 것이 더 나쁩니다 ( for line in iter (p.stdout.readline, '')`대신 사용할 수 있음).
— jfs

1
나는 이것을 osx에서 python 3.4로 테스트했지만 작동하지 않습니다.
— qed

1
@qed : for line in p.stdout:Python 3에서 작동합니다. ''(유니 코드 문자열)과 b''(바이트) 의 차이점을 이해해야합니다 . 참조 파이썬 : subprocess.communicate 입력을 스트리밍 읽기 ()
— JFS

8

당신의 문제는 :

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()

반복자 자체에는 추가 버퍼링이 있습니다.

다음과 같이 시도하십시오.

while True:
  line = p.stdout.readline()
  if not line:
     break
  print line

5

stdout으로 버퍼링되지 않은 상태로 파이프에 인쇄 할 수 없습니다 (표준 출력으로 인쇄하는 프로그램을 다시 작성할 수없는 경우), 그래서 여기 내 해결책이 있습니다 :

stdout을 버퍼링되지 않은 sterr로 리디렉션합니다. '<cmd> 1>&2'해야합니다. 다음과 같이 프로세스를 엽니 다. myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
stdout 또는 stderr과 구별 할 수 없지만 모든 출력이 즉시 표시됩니다.

이것이이 문제를 해결하는 데 도움이되기를 바랍니다.


4
시도해 보셨습니까? 작동하지 않기 때문에 .. stdout이 해당 프로세스에서 버퍼링되면 PIPE 또는 파일로 리디렉션되지 않는 것과 같은 방식으로 stderr로 리디렉션되지 않습니다 ..
— Filipe Pina

5
이것은 명백한 잘못입니다. stdout 버퍼링은 프로그램 자체 내에서 발생합니다. 쉘 구문 1>&2은 프로그램을 시작하기 전에 파일 설명자가 가리키는 파일을 변경합니다. 프로그램 자체는 stdout을 stderr ( 1>&2) 또는 그 반대 ( 2>&1)로 리디렉션하는 것을 구별 할 수 없으므로 프로그램 의 버퍼링 동작에 영향을 미치지 않으며 어떤 방식 으로든 1>&2구문이 쉘에 의해 해석됩니다. subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)을 지정하지 않았기 때문에 실패합니다 shell=True.
— 윌 맨리

사람들이 이것을 읽는 경우 : stdout 대신 stderr을 사용해 보았지만 똑같은 동작을 보여줍니다.
— martinthenext

3

rsync 프로세스에서 stdout을 버퍼링 해제로 변경하십시오.

p = subprocess.Popen(cmd,
                     shell=True,
                     bufsize=0,  # 0=unbuffered, 1=line-buffered, else buffer-size
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)

3
버퍼링은 rsync 측에서 발생하므로 파이썬 측에서 bufsize 속성을 변경해도 도움이되지 않습니다.
— nosklo

14
검색하는 다른 사람에게는 nosklo의 대답이 완전히 잘못되었습니다. rsync의 진행률 표시는 버퍼링되지 않습니다. 실제 문제는 하위 프로세스가 파일 객체를 반환하고 파일 반복기 인터페이스에 bufsize = 0 인 경우에도 내부 버퍼가 잘못 문서화되어 있으므로 버퍼가 채워지기 전에 결과가 필요한 경우 반복적으로 readline ()을 호출해야한다는 것입니다.
— Chris Adams 2012

3

출력 캐싱을 피하기 위해 pexpect를 시도해 볼 수 있습니다.

child = pexpect.spawn(launchcmd,args,timeout=None)
while True:
    try:
        child.expect('\n')
        print(child.before)
    except pexpect.EOF:
        break

추신 : 나는이 질문이 꽤 오래되었고 여전히 나를 위해 일한 솔루션을 제공한다는 것을 알고 있습니다.

PPS : 다른 질문에서이 답변을 받았습니다.


3
    p = subprocess.Popen(command,
                                bufsize=0,
                                universal_newlines=True)

파이썬에서 rsync에 대한 GUI를 작성하고 있으며 동일한 probelm이 있습니다. 이 문제는 pyDoc에서 이것을 찾을 때까지 며칠 동안 나를 괴롭 혔습니다.

universal_newlines가 True이면 파일 객체 stdout 및 stderr은 범용 줄 바꿈 모드에서 텍스트 파일로 열립니다. 줄은 '\ n', Unix 줄 끝 규칙, '\ r', 이전 Macintosh 규칙 또는 '\ r \ n', Windows 규칙 중 하나로 끝날 수 있습니다. 이러한 모든 외부 표현은 Python 프로그램에서 '\ n'으로 표시됩니다.

번역이 진행 중일 때 rsync가 '\ r'을 출력하는 것 같습니다.


1

임시 파일을 중간으로 사용하는 것에 대한 언급이 없다는 것을 알았습니다. 다음은 임시 파일로 출력하여 버퍼링 문제를 해결하고 pty에 연결하지 않고 rsync에서 오는 데이터를 구문 분석 할 수 있도록합니다. Linux 상자에서 다음을 테스트했으며 rsync의 출력은 플랫폼마다 다른 경향이 있으므로 출력을 구문 분석하는 정규식이 다를 수 있습니다.

import subprocess, time, tempfile, re

pipe_output, file_name = tempfile.TemporaryFile()
cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"]

p = subprocess.Popen(cmd, stdout=pipe_output, 
                     stderr=subprocess.STDOUT)
while p.poll() is None:
    # p.poll() returns None while the program is still running
    # sleep for 1 second
    time.sleep(1)
    last_line =  open(file_name).readlines()
    # it's possible that it hasn't output yet, so continue
    if len(last_line) == 0: continue
    last_line = last_line[-1]
    # Matching to "[bytes downloaded]  number%  [speed] number:number:number"
    match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line)
    if not match_it: continue
    # in this case, the percentage is stored in match_it.group(1), 
    # time in match_it.group(2).  We could do something with it here...

실시간이 아닙니다. 파일은 rsync 측의 버퍼링 문제를 해결하지 못합니다.
— jfs

tempfile.TemporaryFile은 예외의 경우 더 쉽게 정리할 수 있도록 자신을 삭제할 수 있습니다
— jfs

3
while not p.poll()하위 프로세스가 0으로 성공적으로 종료되면 무한 루프로 이어지고 p.poll() is None대신 사용
— jfs

Windows가 이미 열린 파일을 열지 못하도록 open(file_name)할 수 있으므로 실패 할 수 있습니다.
— jfs

1
난 그냥 불행하게도 단지 리눅스의 경우,이 답을 발견하지만, 매력처럼 작동 링크를 다음과 같이 난 그냥 내 명령을 확장 그래서 : command_argv = ["stdbuf","-i0","-o0","-e0"] + command_argv및 전화 : popen = subprocess.Popen(cmd, stdout=subprocess.PIPE) 지금은 버퍼링없이 읽을 수 있습니다
— 의 Arvid Terzibaschian에게

0

스레드에서 이와 같은 것을 실행하고 메소드의 속성에 ffmpeg_time 속성을 저장하여 액세스 할 수 있다면 다음과 같은 출력을 얻을 수 있습니다 . 출력은 tkinter에서 스레딩을 사용하는 것과 같습니다.

input = 'path/input_file.mp4'
output = 'path/input_file.mp4'
command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\""
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True)
for line in self.process.stdout:
    reg = re.search('\d\d:\d\d:\d\d', line)
    ffmpeg_time = reg.group(0) if reg else ''
    print(ffmpeg_time)

-1

Python 3에서 다음은 명령 줄에서 명령을 내리고 수신 된대로 멋지게 디코딩 된 문자열을 실시간으로 전달하는 솔루션입니다.

수신기 ( receiver.py) :

import subprocess
import sys

cmd = sys.argv[1:]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
for line in p.stdout:
    print("received: {}".format(line.rstrip().decode("utf-8")))

실시간 출력을 생성 할 수있는 간단한 프로그램의 예 ( dummy_out.py) :

import time
import sys

for i in range(5):
    print("hello {}".format(i))
    sys.stdout.flush()  
    time.sleep(1)

산출:

$python receiver.py python dummy_out.py
received: hello 0
received: hello 1
received: hello 2
received: hello 3
received: hello 4
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.