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