Как получить выходные данные 'print()', 'os.system()' и 'subprocess.run()' для отображения в консоли и в файле журнала?
Изначально у меня есть простая программа для вывода всего вывода на консоль.
Исходный код для отображения вывода только в консоли
import os, subprocess
print("1. Before")
os.system('ver')
subprocess.run('whoami')
print('\n2. After')
Вывод в консоль
1. Before
Microsoft Windows [Version 10]
user01
2. After
Затем я решил сохранить копию файла журнала (log.txt), сохраняя при этом исходный вывод на консоль.
Итак, это новый код.
import os, subprocess, sys
old_stdout = sys.stdout
log_file = open("log.txt","w")
sys.stdout = log_file
print("1. Before") # This appear in message.log only, but NOT in console
os.system('ver') # This appear in console only, but NOT in message.log
subprocess.run('whoami') # This appear in console only, but NOT in message.log
print('\n2. After') # This appear in message.log only, but NOT in console
sys.stdout = old_stdout
log_file.close()
К сожалению, это не сработало, как ожидалось. Некоторые выходные данные отображаются только на консоли (os.system('ver')
а также subprocess.run('whoami')
) в то время как print()
функция была отображена только на log.txt
файл, а не в консоли больше.
Вывод в консоль
Microsoft Windows [Version 10]
user01
Выход в log.txt
файл
1. Before
2. After
Я надеялся получить похожий вывод на консоли и log.txt
файл. Это возможно? Что не так с моим новым кодом? Пожалуйста, дайте мне знать, как это исправить.
Желаемый вывод как в консоли, так и log.txt
файл
1. Before
Microsoft Windows [Version 10]
user01
2. After
2 ответа
Наиболее подходящий способ справиться с этим - ведение журнала. Вот пример:
Это версия Python 2.6+ и 3.x того, как вы можете это сделать. (Невозможно переопределить print()
до 2.6)
log = logging.getLogger()
log.setLevel(logging.INFO)
# How should our message appear?
formatter = logging.Formatter('%(message)s')
# This prints to screen
ch = log.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(formatter)
log.addHandler(ch)
# This prints to file
fh = log.FileHandler('/path/to/output_file.txt')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
log.addHandler(fh)
def print(*args, **kwargs):
log.DEBUG(*args)
Эта опция позволяет вам использовать уровни регистрации. Например, вы можете поместить отладочную логи во весь код, когда приложение начинает работать в стиле фанк. Своп logLevel
в logging.DEBUG
и вдруг вы получаете этот вывод на экран. Обратите внимание, что в приведенном выше примере у нас есть 2 различных уровня ведения журнала, один для экрана и другой для файла. Да, это даст разные результаты для каждого пункта назначения. Вы можете исправить это, изменив оба на использование logging.INFO
(или же logging.DEBUG
, так далее). ( См. Полные документы, относящиеся к уровням журнала здесь.)
В приведенном выше примере, я переопределил print()
, но я бы рекомендовал вместо этого просто ссылаться на свой фреймворк, используя log.DEBUG('Variable xyz: {}'.format(xyz))
или же log.INFO('Some stuff that you want printed.)
Полный logging
документация.
Есть еще один, более простой способ сделать это с переопределением, но не такой надежный:
try:
# Python 2
import __builtin__
except ImportError:
# Python 3
import builtins as __builtin__
logfile = '/path/to/logging_file.log'
def print(*args, **kwargs):
"""Your custom print() function."""
with open(logfile) as f_out:
f_out.write(args[0])
f_out.write('\n')
# Uncomment the below line if you want to tail the log or something where you need that info written to disk ASAP.
# f_out.flush()
return __builtin__.print(*args, **kwargs)
Система не использует магию, указатель файла, такой как stdout и stderr, должен обрабатываться вашим кодом по-разному. Например, stdout является одним из указателей файла, вы можете сделать это ниже:
log_file_pointer = open('log.txt', 'wt')
print('print_to_fp', file=log_file_pointer)
# Note: the print function will actually call log_file_pointer.write('print_to_fp')
Исходя из ваших требований, вы хотите, чтобы волшебная функция обрабатывала более одного указателя файла в одной строке, вам нужна функция-обёртка ниже:
def print_fps(content, files=[]):
for fi in files:
print(content, file=fi)
# the argument `file` of print does zero magic, it can only handle one file pointer once.
Затем вы можете сделать волшебство сейчас (сделать вывод как на экране, так и в файле.)
import sys
log_file_pointer = open('log.txt', 'wt')
print_fps('1. Before', files=[log_file_pointer, sys.stdout])
print_fps('\n2. After', files=[log_file_pointer, sys.stdout])
После окончания print
часть, давайте перейдем к системному вызову. Запустив любую команду в операционной системе, вы получите возврат в указателях системных файлов по умолчанию: stdout и stderr. В python3 вы можете получить эти результаты в байтах с помощью subprocess.Popen. И пока вы выполняете код, приведенный ниже, вы должны получить результат в stdout.
import subprocess
p = subprocess.Popen("whoami", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
# stdout: b'user01'
# stdout: b''
Опять же, вы можете вызвать функцию-оболочку, написанную выше, и сделать вывод как в stdout, так и в target__inter_tinter.
print_fps(stdout, files=[log_file_pointer, sys.stdout])
Наконец, объединяя весь код выше. (Плюс еще одна удобная функция.)
import subprocess, sys
def print_fps(content, files=[]):
for fi in files:
print(content, file=fi)
def get_stdout(command):
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
# Note: Original idea is to return raw stdout
# return stdout
# Based on the scenario of the @Sabrina, the raw bytes of stdout needs decoding in utf-8 plus replacing newline '\r\n' to be pure
return stdout.decode().replace('\r\n', '')
log_file_pointer = open('log.txt', 'wt')
print_fps('1. Before', files=[log_file_pointer, sys.stdout])
print_fps(get_stdout('ver'), files=[log_file_pointer, sys.stdout])
print_fps(get_stdout('whoami'), files=[log_file_pointer, sys.stdout])
print_fps('\n2. After', files=[log_file_pointer, sys.stdout])
- Примечание: поскольку выходные данные Popen представлены в байтах, вам может потребоваться выполнить декодирование для удаления b ''. Вы можете запустить stdout.decode() для декодирования байтов в декодированную строку utf-8.*