从subprocess实时捕获标准输出

我想在Windows中使用subprocess.Popen 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() 

subprocess一些经验法则。

  • 永远不要使用shell=True 。 它不必要地调用一个额外的shell进程来调用你的程序。
  • 调用进程时,参数以列表forms传递。 python中的sys.argv是一个列表,所以C中的argv也是这样。所以你传递一个列表Popen来调用subprocess,而不是一个string。
  • 不读取时,不要将stderrredirect到PIPE
  • 当你不写信给它时,不要redirectstdin

例:

 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检测到它连接到pipe道而不是terminal时,它可能会缓冲它的输出。 这是默认行为 – 连接到pipe道时,程序必须显式刷新stdout以获得实时结果,否则标准C库将缓冲。

要testing这个,请尝试运行它,而不是:

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

并用下面的内容创build一个test_out.py文件:

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

执行该subprocess应该给你“你好”,等待10秒钟,然后给予“世界”。 如果这发生在上面的python代码而不是rsync ,这意味着rsync本身缓冲输出,所以你是运气不好。

解决办法是直接连接到一个pty ,使用像pexpect东西。

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

你不能得到标准输出无缓冲到pipe道(除非你可以重写打印到标准输出的程序),所以这里是我的解决scheme:

将stdoutredirect到未被缓冲的sterr。 '<cmd> 1>&2'应该这样做。 打开过程如下: myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
你不能从stdout或者stderr中区分出来,但是你立即得到所有的输出。

希望这有助于任何人解决这个问题。

 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 

subprocessclosures标准输出或退出时留下while循环。 read()/read(-1)将阻塞,直到subprocessclosures其标准输出或退出。

在Linux上,我有摆脱缓冲的同样的问题。 我终于使用了“stdbuf -o0”(或者,来自expect的unbuffer)来摆脱PIPE缓冲。

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

然后我可以在标准输出上使用select.select。

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

你的问题是:

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

迭代器本身有额外的缓冲。

尝试这样做:

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

将rsync进程的标准输出更改为无缓冲。

 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) 

为了避免caching输出,你可能想试试看,

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

PS :我知道这个问题很老,仍然为我提供了解决scheme。

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

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

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

如果universal_newlines为True,则文件对象stdout和stderr将作为文本文件以通用换行符模式打开。 行可以通过任何'\ n',Unix行尾约定,'\ r',旧的Macintosh约定或Windows约定'\ r \ n'来终止。 所有这些外部表示都被Python程序视为“\ n”。

rsync在翻译过程中会输出'\ r'。

我注意到,没有提到使用临时文件作为中介。 下面通过输出到临时文件解决缓冲问题,并允许您parsing来自rsync的数据而不连接到一个pty。 我在一个Linux机器上testing了以下内容,并且rsync的输出在不同平台之间往往有所不同,所以parsing输出的正则expression式可能会有所不同:

 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... 

使用| tee 在terminal上实时显示stdout时,将stdoutredirect到名为out.txt的文件

 import subprocess, time, os, sys cmd = "rsync.exe -vaz -P source/ dest/ | tee out.txt" p, line = True, 'start' p = subprocess.Popen(cmd, shell=True) p.wait() 

你可以在subprocess之后从文件out.txt获得stdout。

 # Get stdout from file out.txt f = open('out.txt') out = f.read() f.close()