Python Popen: запись в стандартный вывод и лог-файл одновременно

Я использую Popen для вызова сценария оболочки, который постоянно записывает свои stdout и stderr в файл журнала. Есть ли способ одновременно выводить файл журнала непрерывно (на экран) или, альтернативно, заставить сценарий оболочки одновременно записывать в файл журнала и стандартный вывод?

Я в основном хочу сделать что-то подобное в Python:

cat file 2>&1 | tee -a logfile #"cat file" will be replaced with some script

Опять же, этот поток stderr / stdout соединяется с tee, который записывает его и в stdout, и в мой лог-файл.

Я знаю, как записать stdout и stderr в файл журнала на Python. Я застрял в том, как скопировать их обратно на экран:

subprocess.Popen("cat file", shell=True, stdout=logfile, stderr=logfile)

Конечно, я мог бы просто сделать что-то подобное, но есть ли способ сделать это без перенаправления дескриптора файла тройника и оболочки?:

subprocess.Popen("cat file 2>&1 | tee -a logfile", shell=True)

3 ответа

Решение

Вы можете использовать канал, чтобы прочитать данные из стандартного вывода программы и записать их во все нужные вам места:

import sys
import subprocess

logfile = open('logfile', 'w')
proc=subprocess.Popen(['cat', 'file'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
    sys.stdout.write(line)
    logfile.write(line)
proc.wait()

ОБНОВИТЬ

В питоне 3 universal_newlines Параметр контролирует, как используются трубы. Если Falseтруба читает возврат bytes объекты и, возможно, должны быть декодированы (например, line.decode('utf-8')) чтобы получить строку. Если True, Python делает декодирование для вас

Изменено в версии 3.3: Когда universal_newlines имеет значение True, класс использует кодировку locale.getpreferredencoding(False) вместо locale.getpreferredencoding(). См. Класс io.TextIOWrapper для получения дополнительной информации об этом изменении.

Подражать: subprocess.call("command 2>&1 | tee -a logfile", shell=True) без вызова tee команда:

#!/usr/bin/env python2
from subprocess import Popen, PIPE, STDOUT

p = Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1)
with p.stdout, open('logfile', 'ab') as file:
    for line in iter(p.stdout.readline, b''):
        print line,  #NOTE: the comma prevents duplicate newlines (softspace hack)
        file.write(line)
p.wait()

Чтобы исправить возможные проблемы с буферизацией (если вывод задерживается), см. Ссылки в Python: чтение потокового ввода из subprocess.communicate ().

Вот версия Python 3:

#!/usr/bin/env python3
import sys
from subprocess import Popen, PIPE, STDOUT

with Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1) as p, \
     open('logfile', 'ab') as file:
    for line in p.stdout: # b'\n'-separated lines
        sys.stdout.buffer.write(line) # pass bytes as is
        file.write(line)

Запись в терминал побайтово для интерактивных приложений

Я обнаружил, что это имитирует поведение tee более тесно для интерактивных приложений.

test.py:

#!/usr/bin/env python3
import os
import subprocess
import sys
with subprocess.Popen(sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc, \
        open('logfile.txt', 'bw') as logfile:
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            logfile.write(byte)
        else:
            break
# In case you need it.
exit_status = proc.returncode

Например, если вы запустите:

./test.py bash

затем набираемые символы сразу же появляются в терминале по мере их ввода, что очень важно для интерактивных приложений. Вот что происходит при запуске:

bash | tee logfile.txt

Кроме того, если вы хотите, чтобы вывод сразу отображался в выходном файле, вы также можете добавить:

logfile.flush()

но tee не делает этого, и я боюсь, что это убьет производительность. Вы можете легко проверить это с помощью:

tail -f logfile.txt
Другие вопросы по тегам