Python 在popen.stdout.readline上检测流的结尾

Python 在popen.stdout.readline上检测流的结尾,python,python-2.7,popen,Python,Python 2.7,Popen,我有一个python程序,它使用Popen启动子流程,并在生成时几乎实时地消耗它们的输出。相关回路的代码为: def run(self, output_consumer): self.prepare_to_run() popen_args = self.get_popen_args() logging.debug("Calling popen with arguments %s" % popen_args) self.popen = subprocess.Pope

我有一个python程序,它使用
Popen
启动子流程,并在生成时几乎实时地消耗它们的输出。相关回路的代码为:

def run(self, output_consumer):
    self.prepare_to_run()
    popen_args = self.get_popen_args()
    logging.debug("Calling popen with arguments %s" % popen_args)
    self.popen = subprocess.Popen(**popen_args)
    while True:
        outdata = self.popen.stdout.readline()
        if not outdata and self.popen.returncode is not None:
            # Terminate when we've read all the output and the returncode is set
            break
        output_consumer.process_output(outdata)
        self.popen.poll()  # updates returncode so we can exit the loop
    output_consumer.finish(self.popen.returncode)
    self.post_run()

def get_popen_args(self):
    return {
        'args': self.command,
        'shell': False, # Just being explicit for security's sake
        'bufsize': 0,   # More likely to see what's being printed as it happens
                        # Not guarantted since the process itself might buffer its output
                        # run `python -u` to unbuffer output of a python processes
        'cwd': self.get_cwd(),
        'env': self.get_environment(),
        'stdout': subprocess.PIPE,
        'stderr': subprocess.STDOUT,
        'close_fds': True,  # Doesn't seem to matter
    }
这在我的生产机器上非常有效,但在我的开发机器上,当某些子进程完成时,对
.readline()
的调用将挂起。也就是说,它将成功地处理所有输出,包括最后的输出行“processcomplete”,但随后将再次轮询
readline
,并且永不返回。对于我调用的大多数子进程,此方法都正确地存在于开发人员机器上,但对于一个本身调用多个子进程的复杂bash脚本,此方法始终无法退出

值得注意的是,
popen.returncode
在输出结束前的许多行被设置为非
None
(通常为
0
)值。所以我不能在设置了循环后就跳出循环,否则我会丢失在过程结束时吐出的所有东西,并且仍然被缓冲等待读取。问题是,当我在该点刷新缓冲区时,我无法判断何时结束,因为对
readline()
的最后一次调用挂起。调用
read()
也会挂起。调用
read(1)
可以让我读出最后一个字符,但也会挂起在最后一行之后
popen.stdout.closed
总是
False
。我怎么知道我什么时候到了终点

所有系统都在Ubuntu 12.04LTS上运行python 2.7.3。FWIW,
stderr
正在使用
stderr=subprocess.stdout
stdout
合并


为什么不同?它是否由于某种原因无法关闭标准输出?子流程是否能够以某种方式保持其开放性?这可能是因为我是从我的dev box上的终端启动进程,但在生产环境中,它是通过
supervisord
作为守护进程启动的吗?这会改变管道的处理方式吗?如果会,我该如何规范它们?

主代码循环看起来是正确的。这可能是因为另一个进程保持管道打开,所以管道没有关闭。例如,如果脚本启动一个后台进程,该进程将写入标准输出,那么管道将不会关闭。您确定没有其他子进程仍在运行吗


一个想法是在看到
.returncode
已设置时更改模式。一旦您知道主进程已经完成,就可以从缓冲区中读取它的所有输出,但不要被困在等待中。您可以使用读取超时的管道。设置几秒钟的超时时间,您就可以清除缓冲区,而不会在等待子进程时被卡住。

如果不知道导致问题的“一个复杂bash脚本”的内容,就有太多的可能性来确定确切的原因

但是,请关注这样一个事实:如果您在
supervisords
下运行Python脚本,那么如果子进程试图从stdin读取,那么它可能会卡住,或者如果stdin是tty,那么它的行为可能会有所不同,而(我猜想)
supervisords
将从
/dev/null
重定向

这个最小的示例似乎可以更好地处理我的示例
test.sh
运行试图从stdin读取的子进程的情况

import os
import subprocess

f = subprocess.Popen(args='./test.sh',
                     shell=False,
                     bufsize=0,
                     stdin=open(os.devnull, 'rb'),
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
                     close_fds=True)

while 1:
    s = f.stdout.readline()
    if not s and f.returncode is not None:
        break
    print s.strip()
    f.poll()
print "done %d" % f.returncode
否则,您总是可以退回到使用,并在最终输出行显示“进程完成”时退出,尽管这有点麻烦。

如果您使用readline()或read(),它不应该挂起。无需检查returncode或poll()。如果当您知道流程已完成时,它处于挂起状态,则很可能是保持管道打开的子流程,正如其他人之前所说

要对此进行调试,您可以做两件事: *尝试用最少的脚本而不是当前复杂的脚本进行复制,或者
*使用
strace-f-e clone、execve、exit_group运行该复杂脚本,查看该脚本正在启动什么,以及主脚本中是否有任何进程仍在运行(检查主脚本何时调用exit_group,如果strace在此之后仍在等待,则您的子脚本仍在运行)。

为什么要将sdterr设置为STDOUT

对子例程进行communicate()调用的真正好处是,您能够检索包含stdout响应和stderr响应的元组

如果逻辑取决于它们的成功或失败,那么这些可能是有用的


此外,它还将使您免于必须遍历行的痛苦。Communicate()为您提供了一切,并且不会有任何关于是否收到完整消息的未解决问题

我发现调用
read
(或
readline
)有时会挂起,尽管之前调用了
poll
。因此,我求助于调用
select
,以确定是否存在可读数据。但是,如果进程关闭,
select
无超时也会挂起。因此,我在一个半忙循环中调用select,每次迭代都有一个很小的超时(见下文)

我不确定您是否可以将其改编为readline,因为如果缺少最终的
\n
,或者在您关闭其stdin和/或终止它之前,进程没有关闭其stdout,readline可能会挂起。您可以将其封装在生成器中,每次在stdout\u collected中遇到
\n
时,都会生成当前行

还要注意的是,在我的实际代码中,我使用伪终端(pseudoterminals,pty)来包装popen句柄(以更紧密地伪造用户输入),但它应该可以在不使用伪终端的情况下工作

# handle to read from
handle = self.popen.stdout

# how many seconds to wait without data
timeout = 1

begin = datetime.now()
stdout_collected = ""

while self.popen.poll() is None:
    try:
        fds = select.select([handle], [], [], 0.01)[0]
    except select.error, exc:
        print exc
        break

    if len(fds) == 0:
        # select timed out, no new data
        delta = (datetime.now() - begin).total_seconds()
        if delta > timeout:
            return stdout_collected

        # try longer
        continue
    else:
        # have data, timeout counter resets again
        begin = datetime.now()

    for fd in fds:
        if fd == handle:
            data = os.read(handle, 1024)
            # can handle the bytes as they come in here
            # self._handle_stdout(data)
            stdout_collected += data

# process exited
# if using a pseudoterminal, close the handles here
self.popen.wait()

我用bash子流程编写了一个演示,很容易探索。 封闭管道可以通过
readline()
输出中的
'
识别,而空行的输出是
'\n'

from subprocess import Popen, PIPE, STDOUT
p = Popen(['bash'], stdout=PIPE, stderr=STDOUT)
out = []
while True:
    outdata = p.stdout.readline()
    if not outdata:
        break
    #output_consumer.process_output(outdata)
    print "* " + repr(outdata)
    out.append(outdata)
print "* closed", repr(out)
print "* returncode", p.wait()

输入/输出示例:在终止进程之前关闭管道。这就是为什么等待的原因
[prompt] $ python myscript.py
echo abc
* 'abc\n'
exec 1>&- # close stdout
exec 2>&- # close stderr
* closed ['abc\n']
exit
* returncode 0
[prompt] $
echo -n abc
exit
* 'abc'
* closed ['abc']
* returncode 0