Учитывая произвольный исполняемый файл, который может записывать в 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 (), даже если была задействована библиотека.