Как получить выходные данные «print ()», «os.system ()» и «subprocess.run ()» для отображения как в консоли, так и в файле журнала? - PullRequest
0 голосов
/ 05 января 2019

Изначально у меня есть простая программа для вывода всего вывода на консоль.

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

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 ]

0 голосов
/ 05 января 2019

Наиболее подходящий способ справиться с этим - ведение журнала. Вот пример:

Это версия 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)
0 голосов
/ 05 января 2019

Система не использует магию, указатель файла , такой как 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 вы можете получить эти результаты в байтах с помощью подпроцесса. Откройте . И при выполнении кода ниже, что вы хотите, должен быть результат в 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. *
...