【问题标题】:Python: subprocess32 process.stdout.readline() waiting timePython:subprocess32 process.stdout.readline() 等待时间
【发布时间】:2016-04-01 04:23:53
【问题描述】:

如果我使用例如“ls -Rlah /”运行以下函数“run”,我会立即通过 print 语句按预期获得输出

import subprocess32 as subprocess
def run(command):
    process = subprocess.Popen(command,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT)
    try:
        while process.poll() == None:
            print process.stdout.readline()
    finally:
        # Handle the scenario if the parent
        # process has terminated before this subprocess
        if process.poll():
            process.kill()

但是,如果我使用下面的 python 示例程序,它似乎会卡在 process.poll() 或 process.stdout.readline() 上,直到程序完成。我认为它是 stdout.readline() 因为如果我将要输出的字符串数量从 10 增加到 10000(在示例程序中)或在每次打印后添加 sys.stdout.flush() ,运行中的打印函数确实被执行了。

如何使子流程的输出更加实时?

注意:我刚刚发现 python 示例程序在输出时不执行 sys.stdout.flush(),有没有办法让子进程的调用者以某种方式强制执行?

每 5 秒输出 10 个字符串的示例程序。

#!/bin/env python
import time

if __name__ == "__main__":

    i = 0
    start = time.time()
    while True:
        if time.time() - start >= 5:
            for _ in range(10):
                print "hello world" + str(i)
            start = time.time()
            i += 1
        if i >= 3:
            break

【问题讨论】:

  • 即使使用它,我仍然得到相同的结果。注意我在 python 2.7.10 中使用 subprocess32
  • process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 使用以下调用从进程中读取:while process.poll() == None: 打印进程.stdout.readline() 就像上面的例子一样。
  • ahhh :) 而不是执行 print process.stdout.readline() 我为 iter(process.stdout.readline, b"") 中的行执行了但 readline 直到缓冲区才返回脸红了。
  • 进程倾向于以不同的方式缓冲取决于 stdout 是终端还是管道。您正在使用管道,因此孩子将阻塞缓冲区。尝试使用 pty 模块中的伪终端或使用为此类事物构建的 pexpect
  • 1. command 应该是 POSIX 上的列表(例如,"ls -Rlah /".split()) 2. 使用 is None 而不是 == None 3. print line 双换行,使用 print line,(注意:逗号)代替,以抑制第二个不必要的换行符 4. if p.poll(): p.kill() 是错误的。见links in the comment

标签: python python-2.7 subprocess


【解决方案1】:

在大多数系统上,命令行程序行缓冲区或块缓冲区取决于stdout 是终端还是管道。在 unixy 系统上,父进程可以创建一个伪终端来获得类似终端的行为,即使子进程并没有真正从终端运行。您可以使用pty 模块创建伪终端或使用pexpect 模块来简化对交互式程序的访问。

如 cmets 中所述,使用poll 读取行可能会导致数据丢失。一个例子是进程终止时留在标准输出管道中的数据。阅读pty 与管道有点不同,您会发现您需要在孩子关闭时捕获 IOError 才能使其正常工作,如下例所示。

try:
    import subprocess32 as subprocess
except ImportError:
    import subprocess
import pty
import sys
import os
import time
import errno

print("running %s" % sys.argv[1])

m,s = (os.fdopen(pipe) for pipe in pty.openpty())
process = subprocess.Popen([sys.argv[1]],
                           stdin=s,
                           stdout=s,
                           stderr=subprocess.STDOUT)
s.close()

try:
    graceful = False
    while True:
        line = m.readline()
        print line.rstrip()
except IOError, e:
    if e.errno != errno.EIO:
        raise
    graceful = True
finally:
    # Handle the scenario if the parent
    # process has terminated before this subprocess
    m.close()
    if not graceful:
        process.kill()
    process.wait()

【讨论】:

  • 不要使用.poll()it is unnecessary,最后可能会丢失数据)。见Python subprocess readlines() hangs。无关:如果process.poll() 不是None,那么进程已经死了,即process.kill() 应该失败。不要使用.poll() == None,如果需要与None进行比较,请使用.poll() is None
  • @J.F.Sebastian 你是对的!我尽可能地保留了原始代码,但是在您发表评论后,我意识到我最好提供一个更完整的示例。谢谢。
  • 也使用stdin=s,否则某些程序可能无法启用交互模式(和行缓冲)。我不喜欢time.sleep(.1),如果必须使用p.wait(0.1)(我不明白这一点)。幻数而不是errno.EIO 也不好。此外,您基于os.fdopen() 的代码并不完全等同于os.read()-based code(存在一些问题)。
  • @JFSebastian 我不确定为什么专门杀死孩子的代码在原始代码中......我感觉这是一些可能在信号上终止的中间层进程从父进程。如果我们在阅读pty 时被打断,我们需要在等待之前杀死孩子。但是如果管道正常关闭,我们要给孩子时间退出。我会解决的。
  • @J.F.Sebastian 是的,readline 和简单的迭代有相当多的代码支持它们。 readline 让我最幸运。
【解决方案2】:

您应该在脚本中刷新标准输出:

print "hello world" + str(i)
sys.stdout.flush()

当标准输出是终端时,标准输出是行缓冲的。但如果不是,stdout 是块缓冲的,您需要显式刷新它。

如果无法更改脚本的来源,可以使用 Python 的-u 选项(在子进程中):

-u     Force stdin, stdout and stderr to be totally unbuffered. 

你的命令应该是:['python', '-u', 'script.py']

一般来说,这种缓冲发生在用户空间。没有通用的方法来强制应用程序刷新其缓冲区:一些应用程序支持命令行选项(如 Python),另一些支持信号,另一些则不支持任何东西。

一种解决方案可能是模拟一个伪终端,给程序“提示”它们应该在行缓冲模式下运行。不过,这并不是一个适用于所有情况的解决方案。

【讨论】:

  • 我不知道没有终端它是块缓冲的,有用的信息,谢谢!
  • 但是我正在寻找的是有没有办法在非块缓冲区模式下调用子进程?
  • 谢谢,是的!但是安德里亚,有没有更通用的方法来做到这一点?即如果进程不是python,那么我会被卡住。
  • @Har:这适用于 Python 在您的父进程中完成的缓冲。它确实(并且不能)影响子流程。我们所说的缓冲完全由程序本身或它使用的库完成,内核在这里没有作用(因此我们不能将缓冲区大小从一个进程“转移”到另一个进程)
  • 在你的代码中乱扔垃圾很少是正确的答案,甚至在你无法控制的代码上也是不可能的。大多数程序应该只写标准输出并让父控制它的行或块缓冲。除非在 Windows 上,Microsoft 从未提供过好的 pty 解决方案。
【解决方案3】:

对于python以外的东西,你可以尝试使用unbuffer

unbuffer 禁用在程序输出从非交互式程序重定向时发生的输出缓冲。例如,假设您正在通过 od 运行它来查看 fifo 的输出,然后再运行更多。 od -c /tmp/fifo |更多的 在生成一整页输出之前,您将看不到任何内容。 您可以按如下方式禁用此自动缓冲:

unbuffer od -c /tmp/fifo | more

通常,unbuffer 不会从标准输入读取。这在某些情况下简化了 unbuffer 的使用。要在管道中使用 unbuffer,请使用 -p 标志。例子: 过程1 |无缓冲-p 进程2 |进程3

所以在你的情况下:

run(["unbuffer",cmd]) 

文档中列出了一些注意事项,但这是另一种选择。

【讨论】:

    猜你喜欢
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-03
    • 1970-01-01
    • 2021-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多