Как мне перенаправить стандартный вывод в произвольный файл на Python?
Когда долго работающий скрипт Python (например, веб-приложение) запускается из сеанса ssh и выполняется в фоновом режиме, а сеанс ssh закрывается, приложение выдает IOError и завершается сбоем в тот момент, когда оно пытается выполнить запись в стандартный вывод. Мне нужно было найти способ заставить приложение и модули выводиться в файл, а не в стандартный вывод, чтобы предотвратить сбой из-за IOError. В настоящее время я использую nohup для перенаправления вывода в файл, и это выполняет работу, но мне было интересно, есть ли способ сделать это без использования nohup, из любопытства.
Я уже пробовал sys.stdout = open('somefile', 'w'), но, похоже, это не мешает некоторым внешним модулям по-прежнему выводить данные на терминал (или, возможно, sys.stdout = ... строка вообще не запускалась). Я знаю, что это должно работать с более простыми скриптами, на которых я тестировал, но у меня также пока не было времени протестировать веб-приложение.
Переведено автоматически
Ответ 1
Если вы хотите выполнить перенаправление в скрипте Python, установка sys.stdout в файловый объект делает свое дело:
# for python3 import sys withopen('file', 'w') as sys.stdout: print('test')
Гораздо более распространенным методом является использование перенаправления оболочки при выполнении (то же самое в Windows и Linux):
withopen('help.txt', 'w') as f: with redirect_stdout(f): print('it now prints to `help.text`')
Это похоже на:
import sys from contextlib import contextmanager
@contextmanager defredirect_stdout(new_target): old_target, sys.stdout = sys.stdout, new_target # replace sys.stdout try: yield new_target # run some code with the replaced stdout finally: sys.stdout = old_target # restore to the previous value
который можно использовать в более ранних версиях Python. Последняя версия не является повторно используемой. При желании ее можно переделать.
Он не перенаправляет стандартный вывод на уровне файловых дескрипторов, например:
import os from contextlib import redirect_stdout
stdout_fd = sys.stdout.fileno() withopen('output.txt', 'w') as f, redirect_stdout(f): print('redirected to a file') os.write(stdout_fd, b'not redirected') os.system('echo this also is not redirected')
b'not redirected' и 'echo this also is not redirected' не перенаправляются в output.txt файл.
Для перенаправления на уровне файлового дескриптора os.dup2() можно использовать:
import os import sys from contextlib import contextmanager
deffileno(file_or_fd): fd = getattr(file_or_fd, 'fileno', lambda: file_or_fd)() ifnotisinstance(fd, int): raise ValueError("Expected a file (`.fileno()`) or a file descriptor") return fd
@contextmanager defstdout_redirected(to=os.devnull, stdout=None): if stdout isNone: stdout = sys.stdout
stdout_fd = fileno(stdout) # copy stdout_fd before it is overwritten #NOTE: `copied` is inheritable on Windows when duplicating a standard stream with os.fdopen(os.dup(stdout_fd), 'wb') as copied: stdout.flush() # flush library buffers that dup2 knows nothing about try: os.dup2(fileno(to), stdout_fd) # $ exec >&to except ValueError: # filename withopen(to, 'wb') as to_file: os.dup2(to_file.fileno(), stdout_fd) # $ exec > to try: yield stdout # allow code to be run with the redirected stdout finally: # restore stdout to its previous value #NOTE: dup2 makes stdout_fd inheritable unconditionally stdout.flush() os.dup2(copied.fileno(), stdout_fd) # $ exec >&copied
Тот же пример работает и сейчас, если stdout_redirected() используется вместо redirect_stdout():
import os import sys
stdout_fd = sys.stdout.fileno() withopen('output.txt', 'w') as f, stdout_redirected(f): print('redirected to a file') os.write(stdout_fd, b'it is redirected now\n') os.system('echo this is also redirected') print('this is goes back to stdout')
Выходные данные, которые ранее печатались в стандартном выводе, теперь передаются в output.txt пока stdout_redirected() context manager активен.
Примечание: stdout.flush() не очищает буферы C stdio в Python 3, где ввод-вывод реализуется непосредственно в read()/write() системных вызовах. Чтобы очистить все открытые выходные потоки C stdio, вы могли бы вызвать libc.fflush(None) явно, если какое-либо расширение C использует ввод-вывод на основе stdio:
with merged_stderr_stdout(): print('this is printed on stdout') print('this is also printed on stdout', file=sys.stderr)
Примечание: stdout_redirected() смешивает буферизованный ввод-вывод (sys.stdout обычно) и небуферизованный ввод-вывод (операции непосредственно с файловыми дескрипторами). Будьте осторожны, могут возникнуть проблемыбуферизацией.
Чтобы ответить, ваша правка: вы могли бы использовать python-daemon демонизировать свой скрипт и использовать logging module (как предложил @erikb85) вместо print инструкций и просто перенаправлять стандартный вывод для вашего давно работающего скрипта Python, который вы запускаете с помощью nohup сейчас.