2013-02-13 74 views
5

我有一个python程序,它使用Popen启动子进程,并在产生时几乎实时地使用它们的输出。有关循环的代码是:在popen.stdout.readline上检测流的结束

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()挂起当某些子进程结束。也就是说,它会成功处理所有的输出,包括最后一行输出“过程完成”,但会再次轮询readline并永不返回。对于我调用的大多数子进程,此方法在开发机器上正确退出,但始终无法退出,因为一个复杂的bash脚本本身会调用多个子进程。

值得注意的是,popen.returncode在输出结束之前被设置为许多行的非None值(通常为0)。所以我不能只是在设置时跳出循环,否则我会失去在流程结束时吐出的所有东西,并且仍然等待读取。问题是,当我冲洗缓冲区时,我不知道我什么时候结束,因为最后一次致电readline()挂起。调用read()也挂起。打电话给read(1)让我最后一个字符出来,但也在最后一行后挂起。 popen.stdout.closed总是False。我怎么能告诉我什么时候结束?

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

为什么区别?由于某种原因它没有关闭stdout?子子过程可以做些什么来保持它以某种方式打开?难道是因为我正在从我的开发箱上的终端启动进程,但在生产中,它通过supervisord作为守护进程启动?这将改变管道的处理方式吗?如果是的话,我如何使它们正常化?

+0

是不是你正在阅读从不再存在的工艺线的问题? – 2013-02-13 16:24:25

+0

我不这么认为。如果这个错误很简单,它会一直到处都是失败的。 – Leopd 2013-02-13 16:59:57

+0

为什么你不能只打破''不outdata'' – sotapme 2013-02-13 18:55:32

回答

1

主代码循环看起来不错。可能是由于另一个过程使它保持打开状态,管道未关闭。例如,如果脚本启动写入stdout的后台进程,那么管道将不会关闭。你确定没有其他的孩子进程仍在运行吗?

一个想法是在看到.returncode已设置时更改模式。一旦知道主进程已完成,请从缓冲区读取其所有输出,但不要停顿等待。您可以使用select通过超时读取管道。设置几秒钟的超时时间,您可以清除缓冲区而不会阻塞等待子进程。

1

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

但是,如果您在supervisord下运行您的Python脚本,那么它可能会陷入僵局,如果子进程试图从stdin读取,或者如果stdin是tty,其中(我假设)supervisord将从/dev/null重定向。

这个最小的例子似乎更好地处理了我的例子test.sh运行子进程试图从标准输入读取的情况...

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 

否则,你可以随时退回到使用non-blocking read,并摆脱困境,当你得到你的最终输出线说:“过程完成”,尽管这是一个黑客攻击的一位。

2

如果使用readline()或read(),它不应该挂起。无需检查返回码或轮询()。如果知道该过程完成时挂起,那么它很可能是一个子过程,保持管道畅通,正如其他人之前所说的那样。

有两件事情你可以做调试此: *尝试重现最小的脚本,而不是当前复杂的一个,或 *运行是复杂的脚本strace -f -e clone,execve,exit_group,看看那是什么剧本开始,如果任何进程都在主脚本中生存(检查主脚本何时调用exit_group,如果strace在此之后仍在等待,那么您的孩子仍然活着)。

0

为什么将sdterr设置为STDOUT?

在子处理器上进行通信()调用的真正好处是您可以检索包含stdout响应以及stderr代码的元组。

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

此外,它可以让您免去需要遍历线条的痛苦。沟通()给你一切,将有大约没有悬而未决的问题完整的邮件是否被收到

+0

'communic'等待进程终止。我的应用程序需要处理产生的输出。 – Leopd 2013-04-28 23:18:32

1

我发现,尽管先前呼吁poll,调用read(或readline)有时候会被挂起。于是我诉诸于调用select来查明是否有可读的数据。但是,如果进程关闭,没有超时的select也会挂起。所以我在半忙的循环中调用select,每次迭代都有一个很小的超时(见下文)。

我不确定您是否可以将它改写为readline,因为如果最后的\n丢失,或者在关闭stdin和/或终止它之前该进程没有关闭它的stdout,readline可能会挂起。你可以把它包装在一个生成器中,每当你在stdout_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() 
0

我写了一个演示与可以轻松探索bash的子进程。 A 封闭管道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() 
输入/输出的

实施例:终止处理之前关闭管明显。这就是为什么wait()应改为使用的poll()

[prompt] $ python myscript.py 
echo abc 
* 'abc\n' 
exec 1>&- # close stdout 
exec 2>&- # close stderr 
* closed ['abc\n'] 
exit 
* returncode 0 
[prompt] $ 

您的代码做输出这种情况下空字符串的数量巨大。


:快速的最后一行终止过程中没有'\n'

echo -n abc 
exit 
* 'abc' 
* closed ['abc'] 
* returncode 0