从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。 - 不读取时,不要将
stderr
redirect到PIPE
。 - 当你不写信给它时,不要redirect
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检测到它连接到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。
你的问题是:
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()