实时捕获子进程的stdout

120 投票
13 回答
175721 浏览
提问于 2025-04-15 15:16

我想在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"

或者,你也可以把这个设置放在传给Popenenv参数里。

如果你使用的是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 这样的工具。

撰写回答