Python Popen: записывать в файл stdout AND log одновременно
Я использую Popen для вызова оболочки script, которая постоянно записывает свои stdout и stderr в файл журнала. Есть ли способ одновременного вывода файла журнала непрерывно (на экран) или, альтернативно, сделать оболочку script одновременно записывать как файл журнала, так и stdout?
В основном я хочу сделать что-то подобное в 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)
Конечно, я мог бы просто сделать что-то подобное, но есть ли способ сделать это без перенаправления дескриптора файла tee и shell?:
subprocess.Popen("cat file 2>&1 | tee -a logfile", shell=True)
Ответы
Ответ 1
Вы можете использовать канал для чтения данных из программы stdout и записывать ее во все нужные места:
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()
ОБНОВЛЕНИЕ
В python 3 параметр universal_newlines
управляет использованием труб. Если False
, труба читает return bytes
объекты и может потребоваться декодировать (например, line.decode('utf-8')
), чтобы получить строку. Если True
, python выполняет декодирование для вас
Изменено в версии 3.3: Когда universal_newlines имеет значение True, класс использует кодировку locale.getpreferredencoding(False) вместо locale.getpreferredencoding(). Дополнительную информацию об этом изменении см. В классе io.TextIOWrapper.
Ответ 2
Чтобы эмулировать: 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)
Ответ 3
Запись в терминал побайтово для интерактивных приложений
Этот метод записывает любые байты, которые он получает в стандартный вывод, что более точно имитирует поведение tee
, особенно для интерактивных приложений.
main.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)
# logfile.flush()
else:
break
exit_status = proc.returncode
sleep.py
#!/usr/bin/env python3
import sys
import time
for i in range(10):
print(i)
sys.stdout.flush()
time.sleep(1)
Сначала мы можем сделать неинтерактивную проверку работоспособности:
./main.py ./sleep.py
И мы видим это в режиме реального времени.
Далее для интерактивного теста вы можете запустить:
./main.py bash
Затем набираемые символы сразу же появляются в терминале по мере их ввода, что очень важно для интерактивных приложений. Вот что происходит при запуске:
bash | tee logfile.txt
Кроме того, если вы хотите, чтобы вывод сразу отображался в выходном файле, вы также можете добавить:
logfile.flush()
но tee
не делает этого, и я боюсь, что это убьет производительность. Вы можете легко проверить это с помощью:
tail -f logfile.txt
Смежный вопрос: прямой вывод команды подпроцесса
Протестировано на Ubuntu 18.04, Python 3.6.7.