使用subprocess.Popen

时间:2016-06-14 22:01:30

标签: python linux subprocess

我正在为测试套件中的进程编写一个监视器。我需要确定测试是否挂起。

我只需使用subprocess.Popen(...)开始此过程,然后使用Popen.wait(timeout=to)Popen.poll()并保留自己的计时器。但是,测试的执行时间差别很大,这使得不可能有一个好的超时'对所有测试都有意义的价值。

我发现确定测试是否已挂起的一种好方法是暂停'超时'最后一次进程输出任何东西。为此,我考虑使用

process = subprocess.Popen(args='<program>', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ...)

Popen.communicate(),确定stdout和/或stderr何时不是None。问题是Popen.communicate(),没有超时&#39;将等到该过程终止,并且超时&#39;将引发TimeoutExpired例外,我无法确定是否有任何内容被读取。 TimeoutExpired.output是空的,BTW。

我在文档中找不到允许用户执行&#39;读取&#39;手动。此外,该过程通常会有很多输出,因此以stdout=<open_file_descriptor>开头将是有益的,因为我不会担心溢出管道缓冲区。

更新/解决方案:

Popen.stdoutPopen.stderr返回一个&#34;可读的流对象&#34;,可用于手动轮询/选择和阅读。我最终使用select 'Polling Objects',它使用poll()系统调用,如下所示:

import os
import select
import subprocess

p = subprocess.Popen(args="<program>", shell=True, universal_newlines=True,
                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
poll_obj = select.poll()
poll_obj.register(p.stdout, select.POLLIN)
poll_obj.register(p.stderr, select.POLLIN)

while p.poll() is None:
    events = True
    while events:
        events = poll_obj.poll(10)
        for fd, event in events:
            if event & select.POLLIN:
                print("STDOUT: " if fd == p.stdout.fileno() else "STDERR: ")
                print(os.read(fd, 1024).decode())
            # else some other error (see 'Polling Objects')

2 个答案:

答案 0 :(得分:2)

这有点涵盖here ..

基本上你需要使用select()轮询fd以查看他们是否有输入:

#!/usr/bin/python

import fcntl import os import select import subprocess


def setnonblocking(fd):
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    return fd

p = subprocess.Popen("/bin/sh -c 'c=10; while [ $c -gt 0 ]; do echo $c hello; sleep 1; >&2 echo world; sleep 1; let c=$c-1; done'", stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)

process_fds = map(setnonblocking, [p.stdout, p.stderr])

while process_fds:
    readable, writable, exceptional = select.select(process_fds, [], process_fds, 100)
    print "Select: ", readable, writable, exceptional
    print "Exitcode: ", p.poll()
    for fd in readable:
        data = os.read(fd.fileno(), 1024)
        if data == "":  # EOF
            process_fds.remove(fd)
            continue
        if fd == p.stdout:
            print "STDOUT: ",
        if fd == p.stderr:
            print "STDERR: ",
        print data,
    for fd in exceptional:
        process_fds.remove(fd)

输出:

Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] []
Exitcode:  None
STDOUT:  10 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
STDERR:  world
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] []
Exitcode:  None
STDOUT:  9 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
[...]
STDOUT:  1 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
STDERR:  world
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>, <open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  1
使用

os.read()代替fd.read(),因为您需要以非线性方式阅读。 fd.read()等待直到找到换行符 - 但之后你可能会阻止。使用此方法,您还可以拆分stderrstdout

修改:修改以处理EOF p.stdoutp.stderr之前退出的流程

答案 1 :(得分:0)

以下是如何在Python 3中的Unix上实现“自子进程'最后输出以来的超时”:

#!/usr/bin/env python3
import os
import selectors
import sys
from subprocess import Popen, PIPE, _PopenSelector as Selector

timeout = 1  # seconds
with Popen([sys.executable, '-c', '''import time
for i in range(10):  # dummy script
    time.sleep(i)
    print(i, flush=True)
'''], stdout=PIPE, stderr=PIPE) as process:
    pipes = {process.stdout: 1, process.stderr: 2}  # where to echo data
    with Selector() as sel:
        for pipe in pipes:
            os.set_blocking(pipe.fileno(), False)
            sel.register(pipe, selectors.EVENT_READ)
        while pipes:
            events = sel.select(timeout)
            if not events:  # timeout
                process.kill()
            for key, mask in events:
                assert mask == selectors.EVENT_READ
                data = os.read(key.fd, 512)
                if data == b'':  # EOF
                    sel.unregister(key.fileobj)
                    del pipes[key.fileobj]
                else:  # echo data
                    os.write(pipes[key.fileobj], data)

注意:循环不会在process.poll()上终止 - 没有数据丢失。代码使用subprocess作者喜欢的相同选择器,否则可以使用sel = selectors.DefaultSelector()。如果孙子进程可以继承管道,那么你应该更积极地打破超时循环(EOF may be delayed)。要在Python 3.5之前实现os.set_blocking(),您可以使用fcntl

from fcntl import fcntl, F_GETFL, F_SETFL

def set_nonblocking(fd):
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | os.O_NONBLOCK) # set O_NONBLOCK