Как перенаправить подпроцесс python stderr и stdout в несколько файлов?

Я просто хочу перенаправить stderr и stdout в несколько файлов. Например: stderr следует перенаправить в файл_1 и файл_2.

Я использую ниже, чтобы перенаправить вывод в отдельный файл.

subprocess.Popen("my_commands",shell=True,stdout=log_file,stderr=err_file,executable="/bin/bash")

Вышеуказанная вещь перенаправляет stdout и stderr в один файл.
Может ли кто-нибудь сказать, как сделать то же самое (перенаправить вывод на оба файла log_file и err_file, например, stdout, должен перенаправляться как на log_file, так и на err_file и stderr должен перенаправлять на err_file и new_file)

Ответы

Ответ 1

Вы можете создать свой собственный файловый класс, который записывает в несколько файлов. Вот простой пример, с тестом, который перенаправляет sys.stdout и sys.stderr.

import sys

class MultiOut(object):
    def __init__(self, *args):
        self.handles = args

    def write(self, s):
        for f in self.handles:
            f.write(s)

with open('q1', 'w') as f1, open('q2', 'w') as f2, open('q3', 'w') as f3:
    sys.stdout = MultiOut(f1, f2)
    sys.stderr = MultiOut(f3, f2)
    for i, c in enumerate('abcde'):
        print(c, 'out')
        print(i, 'err', file=sys.stderr)

После запуска этого кода здесь содержатся следующие файлы:

q1

a out
b out
c out
d out
e out    

q3

0 err
1 err
2 err
3 err
4 err    

q2

a out
0 err
b out
1 err
c out
2 err
d out
3 err
e out
4 err

FWIW, вы можете даже сделать это, если хотите:

sys.stdout = MultiOut(f1, f2, sys.stdout)
sys.stderr = MultiOut(f3, f2, sys.stderr)

К сожалению, такие файловые объекты, как MultiOut, не могут использоваться с Popen, потому что Popen обращается к файлам через базовый дескриптор файла ОС, т.е. хочет что-то, что ОС считает файлом, поэтому для аргументов Popen файла могут использоваться только объекты Python, которые предоставляют допустимый метод fileno.

Вместо этого мы можем использовать функции Python 3 asyncio для выполнения команды оболочки и одновременного копирования ее вывода stdout и stderr.

Во-первых, здесь просто Bash script, который я использовал для проверки следующего кода Python. Он просто перебирает массив, повторяя содержимое массива в stdout и индексы массива в stderr, как и предыдущий пример Python.

multitest.bsh

#!/usr/bin/env bash

a=(a b c d e)
for((i=0; i<${#a[@]}; i++))
do 
    echo "OUT: ${a[i]}"
    echo "ERR: $i" >&2
    sleep 0.01
done

Выход

OUT: a
ERR: 0
OUT: b
ERR: 1
OUT: c
ERR: 2
OUT: d
ERR: 3
OUT: e
ERR: 4

И вот код Python 3, который запускает multitest.bsh, передает его вывод stdout в файлы q1 и q2, а его вывод stderr - на q3 и q2.

import asyncio
from asyncio.subprocess import PIPE

class MultiOut(object):
    def __init__(self, *args):
        self.handles = args

    def write(self, s):
        for f in self.handles:
            f.write(s)

    def close(self):
        pass

@asyncio.coroutine
def copy_stream(stream, outfile):
    """ Read from stream line by line until EOF, copying it to outfile. """
    while True:
        line = yield from stream.readline()
        if not line:
            break
        outfile.write(line) # assume it doesn't block

@asyncio.coroutine
def run_and_pipe(cmd, fout, ferr):
    # start process
    process = yield from asyncio.create_subprocess_shell(cmd,
        stdout=PIPE, stderr=PIPE, executable="/bin/bash")

    # read child stdout/stderr concurrently
    try:
        yield from asyncio.gather(
            copy_stream(process.stdout, fout),
            copy_stream(process.stderr, ferr))
    except Exception:
        process.kill()
        raise
    finally:
        # wait for the process to exit
        rc = yield from process.wait()
    return rc

# run the event loop
loop = asyncio.get_event_loop()

with open('q1', 'wb') as f1, open('q2', 'wb') as f2, open('q3', 'wb') as f3:
    fout = MultiOut(f1, f2)
    ferr = MultiOut(f3, f2)
    rc = loop.run_until_complete(run_and_pipe("./multitest.bsh", fout, ferr))
loop.close()
print('Return code:', rc)    

После запуска кода, вот что эти файлы содержат:

q1

OUT: a
OUT: b
OUT: c
OUT: d
OUT: e

q3

ERR: 0
ERR: 1
ERR: 2
ERR: 3
ERR: 4

q2

OUT: a
ERR: 0
OUT: b
ERR: 1
OUT: c
ERR: 2
OUT: d
ERR: 3
OUT: e
ERR: 4

Асинхронный код был снят с J.F. Ответ Себастьяна на вопрос Subprocess.Popen: клонирование stdout и stderr как для терминалов, так и для переменных. Спасибо, J.F!

Обратите внимание, что данные записываются в файлы, когда они становятся доступными для запланированных сопрограмм; когда это происходит, зависит от текущей загрузки системы. Поэтому я поставил команду sleep 0.01 в multitest.bsh, чтобы синхронизация строк stdout и stderr была синхронизирована. Без этой задержки строки stdout и stderr в q2 обычно не будут хорошо перемежаться. Возможно, лучший способ достичь этой синхронизации, но я все еще очень начинаю с асинхронным программированием.