与subprocess.Popen()

时间:2019-06-27 14:22:15

标签: python popen python-3.7

给出一个任意可执行文件,该文件可以快速,任意地写入stdout或stderr,例如

#include <stdio.h>

int main(void)
{
    fprintf(stdout, "OUT:1\n");
    fprintf(stdout, "OUT:2\n");
    fprintf(stderr, "ERR:3\n");
    fprintf(stderr, "ERR:4\n");
    fprintf(stdout, "OUT:5\n");
    fprintf(stderr, "ERR:6\n");
    fprintf(stdout, "OUT:7\n");

    return 0;
}

是否可以将stdout和stderr合并,例如。

stdout_master_fd, stdout_slave_fd = pty.openpty()
subprocess.Popen(stdout=stdout_slave_fd, stderr=subprocess.STDOUT)

并且stdout和stderr分开了。

stdout_master_fd, stdout_slave_fd = pty.openpty()
stderr_master_fd, stderr_slave_fd = pty.openpty()
subprocess.Popen(stdout=stdout_slave_fd, stderr=stderr_slave_fd)

仅需调用subprocess.Popen()

有很多类似的问题:

第一个链接使用时间戳尝试将stderr排序为stdout,但是即使使用ptys缓冲似乎也会发生:

stdout - 2019-06-27T23:43:55.337389 - OUT:1
stdout - 2019-06-27T23:43:55.337389 - OUT:2
stdout - 2019-06-27T23:43:55.337389 - OUT:5
stdout - 2019-06-27T23:43:55.337389 - OUT:7
stderr - 2019-06-27T23:43:55.337413 - ERR:3
stderr - 2019-06-27T23:43:55.337413 - ERR:4
stderr - 2019-06-27T23:43:55.337413 - ERR:6

此外,这显然不能处理任意快速的写入。

第二个链接使用poll(),但只读取大约两行,并且确切的数字发生了变化,并且没有正确排序:

STDOUT:root:OUT:1
STDERR:root:ERR:3
STDOUT:root:OUT:2

我认为第二种方法背后有一个更好的前提,但是目前两种方法看起来都不太有前景。

第二种方法的我的版本,是在第二个链接的注释中从jfs修改的。

def execute(cmd):
        import subprocess, pty, os, select, logging

        logging.basicConfig(level=logging.INFO)
        logging.addLevelName(logging.INFO+1, 'STDOUT')
        logging.addLevelName(logging.INFO+2, 'STDERR')
        logger = logging.getLogger()

        stdout_master_fd, stdout_slave_fd = pty.openpty()
        stderr_master_fd, stderr_slave_fd = pty.openpty()

        p = subprocess.Popen(cmd, stdout=stdout_slave_fd, stderr=stderr_slave_fd, close_fds=True)

        os.close(stdout_slave_fd)
        os.close(stderr_slave_fd)

        with os.fdopen(stdout_master_fd)as stdout, os.fdopen(stderr_master_fd) as stderr:
            poll = select.poll()
            poll.register(stdout, select.POLLIN)
            poll.register(stderr, select.POLLIN | select.EPOLLHUP)

            def cleanup(_done=[]):
                if _done:
                    return
                _done.append(1)
                poll.unregister(stderr)
                poll.unregister(stdout)
                assert p.poll() is not None

            read_write = {stdout.fileno(): (stdout.readline, lambda s: logger.log(logging.INFO+1, s)),
                          stderr.fileno(): (stderr.readline, lambda s: logger.log(logging.INFO+2, s))}

            while True:
                events = poll.poll(40)

                if not events and p.poll() is not None:
                    cleanup()
                    break

                for fd, event in events:
                    if event & select.POLLIN:
                        read, write = read_write[fd]
                        line = read()
                        if line:
                            write(line.rstrip())
                    elif event & select.POLLHUP:
                        cleanup()
                    else:
                        assert 0

        p.wait()
        return p

execute(['./test'])

总体结果最好是类似以下内容:

outs['stdout'] == """
OUT:1
OUT:2
OUT:5
OUT:7
"""

outs['stderr'] == """
ERR:3
ERR:4
ERR:6
"""

outs['merged'] == """
OUT:1
OUT:2
ERR:3
ERR:4
OUT:5
ERR:6
OUT:7
"""

直接访问subprocess.Popen()的返回值,即使涉及到库也是如此。

0 个答案:

没有答案