实时捕获子进程的stdout
我想在Windows上用 subprocess.Popen()
来运行 rsync.exe,并在Python中打印输出。
我的代码可以运行,但它只在文件传输完成后才显示进度!我想要实时打印每个文件的传输进度。
现在我在用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()
13 个回答
33
根据你的具体需求,你可能还想在子进程中关闭缓冲。
如果这个子进程是一个Python程序,你可以在调用之前这样做:
os.environ["PYTHONUNBUFFERED"] = "1"
或者,你也可以把这个设置放在传给Popen
的env
参数里。
如果你使用的是Linux或Unix系统,你可以使用stdbuf
这个工具。比如这样:
cmd = ["stdbuf", "-oL"] + cmd
关于stdbuf
或其他选项的更多信息,可以查看这里。
61
我知道这个话题已经讨论很久了,但现在有了解决办法。你可以在使用rsync的时候加上一个选项--outbuf=L。比如:
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())
125
关于使用 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",然后等待 10 秒后再输出 "World"。如果上面的 Python 代码能这样运行,而 rsync
不能,那就说明 rsync
自己在缓冲输出,这样你就没办法了。
一个解决办法是直接连接到一个 pty
,可以使用像 pexpect
这样的工具。