从子进程实时捕获stdout

Joh*_*n A 74 python subprocess stdout

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

nos*_*klo 92

一些经验法则subprocess.

  • 千万不要使用shell=True.它不必要地调用一个额外的shell进程来调用你的程序.
  • 调用进程时,参数作为列表传递.sys.argv在蟒蛇是一个列表,所以是argv在C.所以你传递一个列表Popen调用子进程,而不是一个字符串.
  • 当你不读它时,不要重定向stderr到a 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())
Run Code Online (Sandbox Code Playgroud)

也就是说,rsync很可能在检测到它连接到管道而不是终端时缓冲其输出.这是默认行为 - 当连接到管道时,程序必须显式刷新stdout以获得实时结果,否则标准C库将缓冲.

要测试它,请尝试运行此代码:

cmd = [sys.executable, 'test_out.py']
Run Code Online (Sandbox Code Playgroud)

并创建一个test_out.py包含以下内容的文件:

import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")
Run Code Online (Sandbox Code Playgroud)

执行该子进程应该给你"Hello"并在给出"World"之前等待10秒.如果上面的python代码发生了这种情况而不是rsync,那就意味着rsync它本身就是缓冲输出,所以你运气不好.

解决方案是pty使用类似的东西直接连接到a pexpect.

  • 在Python 2中使用`for line in iter(p.stdout.readline,b'')`而不是`for line in p.stdout`,否则即使源进程没有缓冲其输出,也不会实时读取行. (11认同)
  • @Denis Otkidach:我认为不保证使用`shell = True`.考虑一下 - 你在你的操作系统上调用另一个进程,涉及内存分配,磁盘使用,处理器调度,只是为了**分割字符串**!还有一个你自己加入!! 你可以拆分python,但无论如何都更容易分别编写每个参数.此外,使用列表意味着您不必转义特殊的shell字符:空格,`;`,`>`,`<`,`&`..您的参数可以包含这些字符,您不必担心!除非你正在运行一个只有shell的命令,否则我看不出使用`shell = True`的理由. (9认同)
  • 当你从用户输入的数据构造命令行时,`shell = False`是正确的.但是,当你从可靠来源获得整个命令行时(例如,在脚本中硬编码),`shell = True`也很有用. (7认同)

Elv*_*vin 37

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

  • 这是有效的,应该被投票,以保存未来的读者滚动上面的所有对话框. (3认同)
  • @VectorVictor 它没有解释发生了什么以及为什么会发生。您的程序可能会正常工作,直到: 1. 您添加 `preexec_fn=os.setpgrp` 以使程序在其父脚本中存活 2. 您跳过从进程管道中读取 3. 进程输出大量数据,填充管道4. 你被困了几个小时,试图找出为什么你正在运行的程序*在一段随机的时间后*退出。@nosklo 的回答对我帮助很大。 (2认同)

Lin*_*ing 13

在Linux上,我遇到了摆脱缓冲的同样问题.我终于使用了"stdbuf -o0"(或者来自期望的unbuffer)来摆脱PIPE缓冲.

proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout
Run Code Online (Sandbox Code Playgroud)

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

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

  • 对于任何试图从 Python 获取 C 代码标准输出的人,我可以确认这个解决方案是唯一对我有用的解决方案。需要明确的是,我正在谈论将“stdbuf”、“-o0”添加到 Popen 中现有的命令列表中。 (2认同)

IBu*_*Bue 9

for line in p.stdout:
  ...
Run Code Online (Sandbox Code Playgroud)

总是阻塞,直到下一个换行.

对于"实时"行为,您必须执行以下操作:

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
Run Code Online (Sandbox Code Playgroud)

当子进程关闭其stdout或退出时,将保留while循环. read()/read(-1)将阻止,直到子进程关闭其stdout或退出.


zvi*_*adm 8

你的问题是:

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()
Run Code Online (Sandbox Code Playgroud)

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

尝试这样做:

while True:
  line = p.stdout.readline()
  if not line:
     break
  print line
Run Code Online (Sandbox Code Playgroud)


小智 6

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

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

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

  • 这是完全错误的.stdout缓冲发生在程序本身内.shell语法"1>&2"只是在启动程序之前更改文件描述符指向的文件.程序本身无法区分将stdout重定向到stderr(`1>&2`)或反之亦然(`2>&1`),因此这对程序的缓冲行为没有任何影响.无论哪种方式`1 >&2`语法由shell解释.`subprocess.Popen('<cmd> 1>&2',stderr = subprocess.PIPE)`会失败,因为你没有指定`shell = True`. (5认同)
  • 你试过吗?因为它不起作用..如果stdout在该进程中被缓冲,它将不会被重定向到stderr,就像它没有重定向到PIPE或文件一样. (4认同)

小智 5

    p = subprocess.Popen(command,
                                bufsize=0,
                                universal_newlines=True)
Run Code Online (Sandbox Code Playgroud)

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

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

似乎 rsync 在翻译时会输出 '\r' 。


Alb*_*ert 5

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

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

os.environ["PYTHONUNBUFFERED"] = "1"
Run Code Online (Sandbox Code Playgroud)

或者将其作为env参数传递给Popen

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

cmd = ["stdbuf", "-oL"] + cmd
Run Code Online (Sandbox Code Playgroud)

另请参见这里stdbuf或其他选项。

  • 你拯救了我的一天,感谢 PYTHONUNBUFFERED=1 (3认同)

Wil*_*ill 1

将 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)
Run Code Online (Sandbox Code Playgroud)

  • 对于其他搜索的人来说,nosklo 的答案是完全错误的:rsync 的进度显示没有缓冲;真正的问题是 subprocess 返回一个文件对象,并且文件迭代器接口的内部缓冲区记录很少,即使 bufsize=0 也是如此,如果您在缓冲区填充之前需要结果,则需要重复调​​用 readline() 。 (17认同)
  • 缓冲发生在 rsync 端,更改 python 端的 bufsize 属性不会有帮助。 (3认同)