给出一个任意可执行文件,该文件可以快速,任意地写入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()的返回值,即使涉及到库也是如此。