从子进程实时捕获stdout

时间:2009-10-22 12:14:43

标签: python subprocess stdout

我想在Windows中使用subprocess.Popen() rsync.exe,并在Python中打印stdout。

我的代码有效,但在文件传输完成之前它没有抓住进度!我想实时打印每个文件的进度。

现在使用Python 3.1,因为我听说它应该更好地处理IO。

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

13 个答案:

答案 0 :(得分:86)

subprocess的一些经验法则。

  • 从不使用shell=True。它不必要地调用一个额外的shell进程来调用你的程序。
  • 调用进程时,参数作为列表传递。 python中的sys.argv是一个列表,C中的argv也是如此。所以你将列表传递给Popen来调用子进程,而不是字符串。
  • 当您不阅读时,请勿将stderr重定向到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秒。如果上面的python代码发生了这种情况而不是rsync,那就意味着rsync本身就是缓冲输出,所以你运气不好。

解决方案是使用pty之类的内容直接连接到pexpect

答案 1 :(得分:31)

我知道这是一个古老的话题,但现在有一个解决方案。使用选项--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())

答案 2 :(得分:11)

在Linux上,我遇到了摆脱缓冲的同样问题。我终于用了#34; stdbuf -o0" (或者,从预期中解除缓冲)摆脱PIPE缓冲。

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

然后我可以在stdout上使用select.select。

另见https://unix.stackexchange.com/questions/25372/

答案 3 :(得分:8)

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

当子进程关闭其stdout或退出时,会留下while循环。 read()/read(-1)将阻止子进程关闭其stdout或退出。

答案 4 :(得分:7)

你的问题是:

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 :(得分:5)

你不能把stdout打印到无管道打印(除非你可以重写打印到stdout的程序),所以这是我的解决方案:

将stdout重定向到没有缓冲的sterr。 '<cmd> 1>&2'应该这样做。按如下方式打开流程:myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
你无法区分stdout或stderr,但你可以立即获得所有输出。

希望这可以帮助任何人解决这个问题。

答案 6 :(得分:2)

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

答案 7 :(得分:2)

为了避免缓存输出,你可能想尝试pexpect,

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

PS :我知道这个问题很老了,仍然提供适合我的解决方案。

PPS :从另一个问题得到了这个答案

答案 8 :(得分:2)

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

我正在python中编写rsync的GUI,并且有相同的问题。这个问题困扰了我好几天,直到我在pyDoc中找到它。

  

如果universal_newlines为True,则文件对象stdout和stderr将以通用换行模式打开为文本文件。行可以通过以下任何方式终止:Unix终止约定,&#39; \ r&#39;旧的Macintosh约定或&#39; \ r \ n&# 39;,Windows约定。所有这些外部表征都被视为&#39; \ n&#39;通过Python程序。

似乎rsync会输出&#39; \ r&#39;正在进行翻译。

答案 9 :(得分:1)

根据使用情况,您可能还希望在子流程本身中禁用缓冲。

如果子进程将是Python进程,则可以在调用之前执行此操作:

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

或者将其在env参数中传递给Popen

否则,如果您使用的是Linux / Unix,则可以使用stdbuf工具。例如。喜欢:

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

另请参见here关于stdbuf或其他选项。

答案 10 :(得分:0)

我注意到没有提到使用临时文件作为中间文件。以下通过输出到临时文件来解决缓冲问题,并允许您解析来自rsync的数据而无需连接到pty。我在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...

答案 11 :(得分:0)

如果您在线程中运行类似的操作并将ffmpeg_time属性保存在方法的属性中,以便您可以访问它,那么它将很好用 我得到这样的输出: output be like if you use threading in 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)

答案 12 :(得分:-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