Przekierować standardowe wyjście do pliku w Pythonie?


313

Jak przekierować standardowe wyjście do dowolnego pliku w Pythonie?

Kiedy długo działający skrypt Pythona (np. Aplikacja internetowa) zostanie uruchomiony z sesji ssh i zostanie zablokowany, a sesja ssh zostanie zamknięta, aplikacja podniesie IOError i zakończy się niepowodzeniem w momencie próby zapisu na standardowe wyjście. Musiałem znaleźć sposób, aby aplikacja i moduły były wyprowadzane do pliku, a nie standardowe, aby zapobiec awarii z powodu IOError. Obecnie używam nohup do przekierowywania danych wyjściowych do pliku, i to robi zadanie, ale zastanawiałem się, czy istnieje sposób, aby to zrobić bez użycia nohup, z ciekawości.

Próbowałem już sys.stdout = open('somefile', 'w'), ale nie wydaje się, aby niektóre zewnętrzne moduły nadal wysyłały dane do terminala (a może sys.stdout = ...linia w ogóle nie zadziałała). Wiem, że powinien działać z prostszych skryptów, na których testowałem, ale nie miałem jeszcze czasu na testowanie w aplikacji internetowej.


8
To nie jest tak naprawdę python, to funkcja powłoki. Po prostu uruchom skrypt jakscript.p > file
Falmarri,

Obecnie rozwiązuję problem za pomocą nohup, ale pomyślałem, że może być coś sprytniejszego ...

1
@foxbunny: nohup? Dlaczego po prostu someprocess | python script.py? Po co angażować nohup?
S.Lott,

3
Przepisz printinstrukcje, aby zastosować loggingmoduł ze stdlib. Następnie możesz przekierować dane wyjściowe wszędzie, mieć kontrolę nad tym, ile chcesz danych wyjściowych itp. W większości przypadków kod produkcyjny nie powinien, printale log.
erikbwork

2
Być może lepszym rozwiązaniem tego problemu jest polecenie screen, które zapisze twoją sesję bash i pozwoli ci uzyskać do niej dostęp z różnych uruchomień.
Ryan Amos

Odpowiedzi:


402

Jeśli chcesz dokonać przekierowania w skrypcie Python, ustawienie sys.stdoutobiektu obiektu załatwi sprawę:

import sys
sys.stdout = open('file', 'w')
print('test')

O wiele bardziej powszechną metodą jest użycie przekierowania powłoki podczas wykonywania (to samo w systemach Windows i Linux):

$ python foo.py > file


7
To nie działa from sys import stdout, może dlatego, że tworzy lokalną kopię. Możesz także używać go withnp with open('file', 'w') as sys.stdout: functionThatPrints(). Z Możesz teraz wdrożyć functionThatPrints()za pomocą normalnych printinstrukcji.
mgold

41
Najlepiej zachować lokalną kopię, stdout = sys.stdoutdzięki czemu można umieścić go z powrotem kiedy skończysz, sys.stdout = stdout. W ten sposób, jeśli jesteś wywoływany z funkcji, która używa print, nie spieprzysz ich.
mgold

4
@ Jan: buffering=0wyłącza buforowanie (może to negatywnie wpłynąć na wydajność (10-100 razy)). buffering=1włącza buforowanie linii, dzięki czemu można użyć tail -fdo wyjścia zorientowanego liniowo.
jfs

41
@mgold lub możesz użyć, sys.stdout = sys.__stdout__aby go odzyskać.
clemtoy,

175

W Pythonie 3.4 jest contextlib.redirect_stdout()funkcja :

from contextlib import redirect_stdout

with open('help.txt', 'w') as f:
    with redirect_stdout(f):
        print('it now prints to `help.text`')

To jest podobne do:

import sys
from contextlib import contextmanager

@contextmanager
def redirect_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

które mogą być używane we wcześniejszych wersjach Pythona. Ta ostatnia wersja nie nadaje się do ponownego użycia . W razie potrzeby można go utworzyć.

Nie przekierowuje standardowego wyjścia na poziomie deskryptorów plików, np .:

import os
from contextlib import redirect_stdout

stdout_fd = sys.stdout.fileno()
with open('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'i 'echo this also is not redirected'nie są przekierowywani do output.txtpliku.

Aby przekierować na poziomie deskryptora pliku, os.dup2()można użyć:

import os
import sys
from contextlib import contextmanager

def fileno(file_or_fd):
    fd = getattr(file_or_fd, 'fileno', lambda: file_or_fd)()
    if not isinstance(fd, int):
        raise ValueError("Expected a file (`.fileno()`) or a file descriptor")
    return fd

@contextmanager
def stdout_redirected(to=os.devnull, stdout=None):
    if stdout is None:
       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
            with open(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

Ten sam przykład działa teraz, jeśli stdout_redirected()zostanie użyty zamiast redirect_stdout():

import os
import sys

stdout_fd = sys.stdout.fileno()
with open('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')

Wyjście, które poprzednio było drukowane na standardowym wyjściu, przechodzi teraz output.txttak długo, jak długo stdout_redirected()aktywny jest menedżer kontekstu.

Uwaga: stdout.flush()nie opróżnia buforów C stdio w Pythonie 3, w którym operacje we / wy są implementowane bezpośrednio w wywołaniach read()/ write()systemowych. Aby opróżnić wszystkie otwarte strumienie wyjściowe C stdio, możesz wywołać libc.fflush(None)jawnie, jeśli niektóre rozszerzenia C używają I / O opartych na stdio:

try:
    import ctypes
    from ctypes.util import find_library
except ImportError:
    libc = None
else:
    try:
        libc = ctypes.cdll.msvcrt # Windows
    except OSError:
        libc = ctypes.cdll.LoadLibrary(find_library('c'))

def flush(stream):
    try:
        libc.fflush(None)
        stream.flush()
    except (AttributeError, ValueError, IOError):
        pass # unsupported

Możesz użyć stdoutparametru do przekierowania innych strumieni, nie tylko sys.stdoutnp. Do scalenia sys.stderri sys.stdout:

def merged_stderr_stdout():  # $ exec 2>&1
    return stdout_redirected(to=sys.stdout, stdout=sys.stderr)

Przykład:

from __future__ import print_function
import sys

with merged_stderr_stdout():
     print('this is printed on stdout')
     print('this is also printed on stdout', file=sys.stderr)

Uwaga: stdout_redirected()miksuje buforowane sys.stdoutoperacje we / wy ( zwykle) i niebuforowane operacje we / wy (operacje na deskryptorach plików bezpośrednio). Uwaga, mogą wystąpić problemy z buforowaniem .

Aby odpowiedzieć, edytuj: możesz użyć python-daemondo demonizacji skryptu i użyć loggingmodułu (jak sugerowano @ erikb85 ) zamiast printinstrukcji i po prostu przekierować standardowe wyjście dla długoterminowego skryptu Python, którego używasz nohupteraz.


3
stdout_redirectedjest pomocny. Pamiętaj, że to nie działa w ramach doctests, ponieważ specjalny SpoofOutprogram obsługi doctest, który zastępuje sys.stdout, nie ma filenoatrybutu.
Chris Johnson

@ChrisJohnson: Jeśli się nie podbije ValueError("Expected a file (`.fileno()`) or a file descriptor"), oznacza to błąd. Jesteś pewien, że to go nie podnosi?
jfs

Podnosi ten błąd, który sprawia, że ​​nie nadaje się on do użytku w ramach testu dokumentów. Aby użyć funkcji w ramach testu dokumentów, wydaje się konieczne określenie, doctest.sys.__stdout__gdzie normalnie byśmy korzystali sys.stdout. To nie jest problem z twoją funkcją, tylko zakwaterowanie wymagane dla doctest, ponieważ zastępuje stdout obiektem, który nie ma wszystkich atrybutów, jakie miałby prawdziwy plik.
Chris Johnson

stdout_redirected()ma stdoutparametr, możesz ustawić go na, sys.__stdout__jeśli chcesz przekierować oryginalny stdout python (który powinien mieć poprawny .fileno()w większości przypadków). Nie robi nic dla prądu, sys.stdoutjeśli są różne. Nie używaj doctest.sys; jest dostępny przez przypadek.
jfs

To naprawdę działa dobrze, tj. Przekieruj stdout i stderr na fd: with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
neok

90

możesz spróbować tego o wiele lepiej

import sys

class Logger(object):
    def __init__(self, filename="Default.log"):
        self.terminal = sys.stdout
        self.log = open(filename, "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)

sys.stdout = Logger("yourlogfilename.txt")
print "Hello world !" # this is should be saved in yourlogfilename.txt

Wszelkie sugestie dotyczące orurowania do loggerlub syslog?
dsummersl

Jeśli chcesz edytować plik, nie jest to bardzo przydatne. W każdym razie +1 za fajną sztuczkę
aIKid

10
Będzie to miało konsekwencje dla kodu, który zakłada, że ​​sys.stdout jest pełnoprawnym obiektem pliku z metodami takimi jak fileno () (który zawiera kod w standardowej bibliotece Pythona). Dodałbym metodę __getattr __ (self, attr) do tej, która odkłada wyszukiwanie do self.terminal. def __getattr__(self, attr): return getattr(self.terminal, attr)
peabody

4
Musisz także dodać def flush(self):metodę do klasy Logger.
loretoparisi

1
@loretoparisi, ale co tak naprawdę wchodzi w twoją metodę?
elkshadow5

28

Pozostałe odpowiedzi nie obejmowały przypadku, w którym chcesz, aby rozwidlone procesy współużytkowały Twój nowy standard.

Aby to zrobić:

from os import open, close, dup, O_WRONLY

old = dup(1)
close(1)
open("file", O_WRONLY) # should open on 1

..... do stuff and then restore

close(1)
dup(old) # should dup to 1
close(old) # get rid of left overs

3
należy zastąpić atrybut „w” os.O_WRONLY | os.O_CREATE ... nie można wysyłać ciągów do poleceń „os”!
Ch'marr

3
Wstaw instrukcję sys.stdout.flush()przed close(1)instrukcją, aby upewnić się, że 'file'plik przekierowania otrzymuje dane wyjściowe. Możesz także użyć tempfile.mkstemp()pliku zamiast 'file'. I bądź ostrożny, nie masz uruchomionych innych wątków, które mogłyby ukraść pierwszy uchwyt pliku systemu operacyjnego po, os.close(1)ale przed 'file'otwarciem, aby użyć uchwytu.
Alex Robinson

2
jego os.O_WRONLY | os.O_CREAT ... tam nie ma E.
Jeff Sheffield


@ Ch'marr To jest O_CREAT, a nie O_CREATE.
quant_dev

28

Cytat z PEP 343 - Instrukcja „z” (dodano instrukcję importu):

Tymczasowo przekieruj standardowe wyjście:

import sys
from contextlib import contextmanager
@contextmanager
def stdout_redirected(new_stdout):
    save_stdout = sys.stdout
    sys.stdout = new_stdout
    try:
        yield None
    finally:
        sys.stdout = save_stdout

Używany w następujący sposób:

with open(filename, "w") as f:
    with stdout_redirected(f):
        print "Hello world"

Oczywiście nie jest to bezpieczne dla wątków, ale nie wykonuje tego samego tańca ręcznie. W programach jednowątkowych (na przykład w skryptach) jest to popularny sposób robienia rzeczy.


1
+1. Uwaga: nie działa w przypadku podprocesów, np os.system('echo not redirected'). Moja odpowiedź pokazuje, jak przekierować takie wyjście
jfs

począwszy od Python 3.4 jest redirect_stdoutwcontextlib
Walter Tross


3

Oto wariant odpowiedzi Yudy Prawiry :

  • implement flush()i wszystkie atrybuty pliku
  • napisz to jako menedżera kontekstu
  • uchwycić stderrrównież

.

import contextlib, sys

@contextlib.contextmanager
def log_print(file):
    # capture all outputs to a log file while still printing it
    class Logger:
        def __init__(self, file):
            self.terminal = sys.stdout
            self.log = file

        def write(self, message):
            self.terminal.write(message)
            self.log.write(message)

        def __getattr__(self, attr):
            return getattr(self.terminal, attr)

    logger = Logger(file)

    _stdout = sys.stdout
    _stderr = sys.stderr
    sys.stdout = logger
    sys.stderr = logger
    try:
        yield logger.log
    finally:
        sys.stdout = _stdout
        sys.stderr = _stderr


with log_print(open('mylogfile.log', 'w')):
    print('hello world')
    print('hello world on stderr', file=sys.stderr)

# you can capture the output to a string with:
# with log_print(io.StringIO()) as log:
#   ....
#   print('[captured output]', log.getvalue())

2

W oparciu o tę odpowiedź: https://stackoverflow.com/a/5916874/1060344 , oto inny sposób, w jaki zorientowałem się, którego używam w jednym z moich projektów. Niezależnie od tego, co zastąpisz sys.stderrlub sys.stdoutczym będziesz musiał, upewnij się, że zamiana jest zgodna z fileinterfejsem, szczególnie jeśli robisz to, ponieważ stderr / stdout są używane w innej bibliotece, która nie jest pod twoją kontrolą. Ta biblioteka może używać innych metod obiektu pliku.

Sprawdź ten sposób, w którym wciąż pozwalam, aby wszystko poszło na stderr / stdout (lub dowolny inny plik w tym przypadku), a także wysłać wiadomość do pliku dziennika za pomocą funkcji rejestrowania Pythona (ale naprawdę możesz to zrobić za pomocą tego):

class FileToLogInterface(file):
    '''
    Interface to make sure that everytime anything is written to stderr, it is
    also forwarded to a file.
    '''

    def __init__(self, *args, **kwargs):
        if 'cfg' not in kwargs:
            raise TypeError('argument cfg is required.')
        else:
            if not isinstance(kwargs['cfg'], config.Config):
                raise TypeError(
                    'argument cfg should be a valid '
                    'PostSegmentation configuration object i.e. '
                    'postsegmentation.config.Config')
        self._cfg = kwargs['cfg']
        kwargs.pop('cfg')

        self._logger = logging.getlogger('access_log')

        super(FileToLogInterface, self).__init__(*args, **kwargs)

    def write(self, msg):
        super(FileToLogInterface, self).write(msg)
        self._logger.info(msg)

2

Potrzebujesz multipleksera terminala, takiego jak ekran Tmux lub GNU

Dziwi mnie, że niewielki komentarz Ryana Amosa do pierwotnego pytania jest jedyną wzmianką o rozwiązaniu zdecydowanie lepszym od wszystkich pozostałych w ofercie, bez względu na to, jak sprytne może być podstępne pytanie i ile otrzymali głosów poparcia. W nawiązaniu do komentarza Ryana tmux jest dobrą alternatywą dla ekranu GNU.

Ale zasada jest taka sama: jeśli kiedykolwiek wylogujesz się z pracy terminalu, udaj się do kawiarni na kanapkę, pop w łazience, idź do domu (itp.), A następnie ponownie połącz się z sesja z dowolnego terminala lub dowolnym komputerze tak, jakbyś nigdy nie był daleko, multipleksery zaciskowe są odpowiedź. Pomyśl o nich jak o VNC lub zdalnym pulpicie dla sesji terminalowych. Wszystko inne jest obejściem. Jako bonus, gdy pojawi się szef i / lub partner i przypadkowo ctrl-w / cmd-w okno terminalu zamiast okna przeglądarki z jego podejrzaną zawartością, nie stracisz ostatnich 18 godzin przetwarzania !


4
choć jest to dobra odpowiedź na tę część pytania, która pojawiła się po edycji; nie odpowiada na pytanie w tytule (większość ludzi przychodzi tutaj z google w celu uzyskania tytułu)
jfs

0

Programy napisane w innych językach (np. C) muszą wykonywać specjalną magię (zwaną podwójnym rozwidleniem), aby odłączyć się od terminala (i zapobiec procesom zombie). Myślę więc, że najlepszym rozwiązaniem jest ich emulacja.

Zaletą ponownego uruchamiania programu jest to, że możesz wybrać przekierowania w wierszu poleceń, np /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

Zobacz ten post, aby uzyskać więcej informacji: Jaki jest powód wykonania podwójnego rozwidlenia podczas tworzenia demona?


Ech ... nie mogę powiedzieć, że jestem fanem procesów zarządzających ich podwójnym rozwidleniem. Jest to tak powszechny idiom i tak łatwe do błędnego kodowania, jeśli nie jesteś ostrożny. Lepiej napisać proces, aby działał na pierwszym planie, i użyj menedżera zadań systemowych w tle ( systemd, upstart) lub innego narzędzia ( daemon(1)) do obsługi rozwidlonej płyty kotłowej.
Lucretiel
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.