Stdout'u Python'daki bir dosyaya yönlendirilsin mi?


314

Stdout'u Python'daki rastgele bir dosyaya nasıl yeniden yönlendirebilirim?

Ssh oturumundan uzun süredir çalışan bir Python betiği (örn. Web uygulaması) başlatıldığında ve yedeklendiğinde ve ssh oturumu kapatıldığında, uygulama IOError değerini yükseltir ve stdout'a yazmaya çalıştığı anda başarısız olur. Uygulama ve modüller IOError nedeniyle başarısızlığı önlemek için stdout yerine bir dosyaya çıktı yapmak için bir yol bulmak gerekiyordu. Şu anda çıktıyı bir dosyaya yönlendirmek için nohup kullanıyorum ve bu işi hallediyor, ancak meraktan çıkmadan nohup kullanmadan yapmanın bir yolu olup olmadığını merak ediyordum.

Zaten denedim sys.stdout = open('somefile', 'w'), ama bu hala bazı harici modüller hala çıkış terminali (ya da belki de sys.stdout = ...hattı hiç ateş vermedi) önlemek gibi görünmüyor . Test ettiğim daha basit komut dosyalarından çalışması gerektiğini biliyorum, ancak henüz bir web uygulamasında test etmek için henüz zamanım olmadı.


8
Bu gerçekten bir piton işi değil, bir kabuk işlevi. Senaryonuzu aşağıdaki gibi çalıştırınscript.p > file
Falmarri

Şu anda nohup kullanarak sorunu çözmek, ama daha akıllıca bir şey olabileceğini düşündüm ...

1
@foxbunny: nohup? Neden basit someprocess | python script.py? Neden dahil nohup?
S.Lott

3
Modülü stdlib'den printuygulamak için ifadeleri yeniden yazın logging. O zaman olmamalı Çoğu durumda üretim kodu vb ne kadar istediğini çıktı üzerinde kontrole sahip, her yerde çıkışını yönlendirebilirsiniz printama log.
erikbwork

2
Belki de bu sorun için daha iyi bir çözüm, bash oturumunuzu kaydedecek ve farklı çalışmalardan erişmenize izin veren ekran komutudur.
Ryan Amos

Yanıtlar:


403

Python betiği içindeki yeniden yönlendirmeyi yapmak istiyorsanız, sys.stdoutbir dosya nesnesine ayarlamak hile yapar:

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

Çok daha yaygın bir yöntem, yürütme sırasında kabuk yeniden yönlendirmesini kullanmaktır (Windows ve Linux'ta aynıdır):

$ python foo.py > file


7
from sys import stdoutBelki yerel bir kopya oluşturduğu için çalışmıyor . Ayrıca with, örn with open('file', 'w') as sys.stdout: functionThatPrints(). Artık functionThatPrints()normal printifadeleri kullanarak uygulayabilirsiniz .
mgold

41
Yerel bir kopyasını saklamak en iyisidir, stdout = sys.stdoutböylece işiniz bittiğinde geri koyabilirsiniz sys.stdout = stdout. Bu şekilde, kullanan bir işlevden çağrılırsanız, printonları mahvetmeyin.
mgold

4
@Jan: buffering=0arabelleğe almayı devre dışı bırakır (performansı olumsuz etkileyebilir (10-100 kez)). buffering=1satır tail -fodaklı bir çıktı için kullanabilmeniz için satır arabelleğe almayı etkinleştirir .
jfs

41
@mgold veya sys.stdout = sys.__stdout__geri almak için kullanabilirsiniz .
clemtoy

176

Python 3.4'te contextlib.redirect_stdout()fonksiyon var :

from contextlib import redirect_stdout

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

Benzer:

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

önceki Python sürümlerinde kullanılabilir. İkinci sürüm yeniden kullanılamaz . İstenirse yapılabilir.

Stdout'u dosya tanıtıcıları düzeyinde yeniden yönlendirmez, örn:

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've dosyaya 'echo this also is not redirected'yönlendirilmez output.txt.

Dosya tanımlayıcı düzeyinde yeniden yönlendirmek os.dup2()için kullanılabilir:

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

Aynı örnek şunun stdout_redirected()yerine kullanılırsa şimdi çalışır 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')

Daha önce stdout'a yazdırılan çıktı artık bağlam yöneticisi aktif output.txtolduğu sürece devam ediyor stdout_redirected().

Not: stdout.flush()G / Ç'nin doğrudan read()/ write()sistem çağrılarına uygulandığı Python 3'teki C stdio tamponlarını temizlemez . Tüm açık C stdio çıkış akışlarını temizlemek için, libc.fflush(None)bazı C uzantılarında stdio tabanlı G / Ç kullanılıyorsa açıkça çağrı yapabilirsiniz :

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

Sen kullanabilirsiniz stdoutdiğer akışları, sadece yönlendirmek için parametre sys.stdoutbirleştirme örneğin, sys.stderrve sys.stdout:

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

Misal:

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)

Not: stdout_redirected()arabelleğe alınmış G / Ç ( sys.stdoutgenellikle) ve arabelleğe alınmamış G / Ç'yi (doğrudan dosya tanımlayıcılarındaki işlemler) karıştırır. Dikkat edin, arabelleğe alma sorunları olabilir .

Yanıtlamak için, düzenlemenizi kullanın: python-daemonkomut dosyanızı arka plana aktarmak ve ifadeler yerine loggingmodülü ( @ erikb85 önerdiği gibi ) kullanmak printve yalnızca nohupşimdi kullandığınız uzun süredir çalışan Python komut dosyanız için stdout'u yeniden yönlendirmek için kullanabilirsiniz .


3
stdout_redirectedyardımcı olur. Bunun doctestler içinde çalışmadığını unutmayın, çünkü SpoofOutdoctest'in değiştirmek için kullandığı özel işleyici sys.stdoutbir filenoniteliğe sahip değildir .
Chris Johnson

@ChrisJohnson: Eğer yükselmezse ValueError("Expected a file (`.fileno()`) or a file descriptor"), bu bir hatadır. Yükseltmediğinden emin misin?
jfs

Bu hatayı ortaya çıkarır, bu da onu bir doctest içinde kullanılamaz kılan şeydir. İşlevinizi bir doctest içinde kullanmak için, doctest.sys.__stdout__normalde nerede kullanacağımızı belirtmeniz gerekir sys.stdout. Bu işlevinizle ilgili bir sorun değildir, stdout'u gerçek bir dosyanın tüm niteliklerine sahip olmayan bir nesneyle değiştirdiği için doctest için gereken bir konaklama yeri.
Chris Johnson

stdout_redirected()sahip stdoutparametre, sen için ayarlayabilirsiniz sys.__stdout__Eğer (geçerli olmalıdır orijinal piton stdout'u yönlendirmek istiyorsanız .fileno()birçok durumda). sys.stdoutFarklılarsa akım için hiçbir şey yapmaz . Kullanmayın doctest.sys; kazara kullanılabilir.
jfs

Bu gerçekten iyi çalışıyor, yani stdout ve with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
stderr'i bir fd'ye

91

bunu çok daha iyi deneyebilirsin

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

Herhangi bir öneri loggerveya syslog?
dsummersl

Bir dosyayı düzenlemek istiyorsanız bu çok kullanışlı değildir. Neyse güzel hile için +1
aIKid

10
Bu, sys.stdout'un fileno () (python standart kitaplığında kod içeren) gibi yöntemlerle tam teşekküllü bir dosya nesnesi olduğunu varsayan kod için sonuçları olacaktır. Ben self.terminal öznitelik aramayı savunan bir __getattr __ (self, attr) yöntemi eklemek istiyorum. def __getattr__(self, attr): return getattr(self.terminal, attr)
peabody

4
def flush(self):Sınıfa da yöntem eklemelisiniz Logger.
loretoparisi

1
@loretoparisi ama aslında yarattığınız yönteme ne oluyor?
elkshadow5

28

Diğer cevaplar, çatallı süreçlerin yeni stdout'unuzu paylaşmasını istediğiniz durumu kapsamıyordu.

Bunu yapmak için:

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
biri 'w' özniteliği ile değiştirilmelidir, os.O_WRONLY | os.O_CREATE ... "os" komutlarına dize gönderemez!
Ch'marr

3
Yönlendirme dosyasının çıktıyı aldığından emin olmak sys.stdout.flush()için close(1)deyimin önüne bir ekleyin 'file'. Ayrıca, tempfile.mkstemp()yerine bir dosya kullanabilirsiniz 'file'. Dikkatli olun, iş parçacığını kullanmak için açılmadan os.close(1)önce ancak 'file'tutamacı kullanmak için açılmadan önce, os'un ilk dosya tanıtıcısını çalabilecek başka iş parçacıklarınız olmadığına dikkat edin .
Alex Robinson

2
os.O_WRONLY | os.O_CREAT ... orada E yok.
Jeff Sheffield

+1. Ayrıca os.dup2()
cevabımda

@ Ch'marr O_CREAT değil, O_CREAT.
quant_dev

28

Alıntı sahibi PEP 343 - "with" Deyimi (eklenen ithalat bildirimi):

Stdout'u geçici olarak yeniden yönlendir:

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

Aşağıdaki gibi kullanılır:

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

Bu elbette iş parçacığı için güvenli değildir, ancak ikisi de aynı dansı elle yapmaz. Tek iş parçacıklı programlarda (örneğin komut dosyalarında), bir şeyler yapmanın popüler bir yoludur.


1
+1. Not: alt işlemler için çalışmaz, örn os.system('echo not redirected'). Cevabım böyle bir çıktıyı nasıl yönlendireceğinizi gösteriyor
jfs

Python 3.4 başlayarak var redirect_stdoutiçindecontextlib
Walter Tross


3

İşte Yuda Prawira cevabının bir çeşidi :

  • uygulamak flush()ve tüm dosya öznitelikleri
  • onu bağlam yöneticisi olarak yaz
  • stderrayrıca yakala

.

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

Bu cevaba dayanarak: https://stackoverflow.com/a/5916874/1060344 , projelerimden birinde kullandığım başka bir yol var. Ne değiştirirseniz sys.stderrveya neyle değiştirirseniz değiştirin , özellikle bu yaptığınız bir şeyse sys.stdoutdeğiştirmenin filearayüzle uyumlu olduğundan emin olmalısınız çünkü stderr / stdout kontrolünüz altında olmayan başka bir kütüphanede kullanılır. Bu kütüphane başka dosya nesnesi yöntemleri kullanıyor olabilir.

Ben hala her şeyin stderr / stdout (ya da bu konuda herhangi bir dosya) yapmasına izin vermek ve ayrıca Python günlüğü tesisini kullanarak bir günlük dosyasına mesaj göndermek (ama gerçekten bununla her şeyi yapabilirsiniz) bu şekilde göz atın:

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

Tmux veya GNU ekran gibi bir terminal çoklayıcısına ihtiyacınız var

Ryan Amos'un orijinal soruya yaptığı küçük bir yorumun, python hilesinin ne kadar akıllı olabileceği ve kaç tane upvotes aldıkları fark etmeksizin, sunulan diğerlerine göre çok tercih edilen bir çözümün tek sözü olması beni şaşırttı. Ryan'ın yorumuna ek olarak, tmux GNU ekranına güzel bir alternatif.

Ancak ilke aynıdır: Oturumu kapatırken bir terminal işini terk etmek istediğinizi düşünüyorsanız, bir sandviç için kafeye gidin, banyoya gidin, eve gidin (vb.) Ve daha sonra tekrar herhangi bir yerden veya herhangi bir bilgisayardan terminal oturumu hiç uzakta olmamış gibi , cevap çoklayıcı terminaller . Bunları terminal oturumları için VNC veya uzak masaüstü olarak düşünün. Başka bir şey geçici bir çözümdür. Bonus olarak, patron ve / veya ortak içeri girdiğinde ve tehlikeli içeriğiyle tarayıcı pencereniz yerine terminal pencerenizi yanlışlıkla ctrl-w / cmd-w yaptığınızda, son 18 saatlik işlem kaybını kaybetmezsiniz !


4
düzenlemeden sonra ortaya çıkan soru kısmı için iyi bir cevap olsa da; başlıktaki soruya cevap vermez (çoğu kişi buraya başlık için google'dan gelir)
jfs

0

Diğer dillerde yazılmış programlar (örneğin C) terminalden ayrılmak (ve zombi işlemlerini önlemek için) özel büyü (çift çatal adı verilir) yapmak zorundadır. Bence en iyi çözüm onları taklit etmektir.

Programınızı yeniden çalıştırmanın bir artısı, komut satırında yeniden yönlendirmeler seçebilirsiniz, örn. /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

Daha fazla bilgi için bu gönderiye bakın: Bir daemon oluştururken çift çatal yapmanın nedeni nedir?


Eh ... kendi çift çatallarını yöneten süreçlerin hayranı olduğumu söyleyemem. Çok yaygın bir deyim ve dikkatli değilseniz yanlış kodlamak çok kolaydır. Prosesinizi ön planda çalıştırmak için yazmak daha iyi ve çatallı levhayı işlemek için bir sistem arka plan görev yöneticisi ( systemd, upstart) veya başka bir yardımcı program ( daemon(1)) kullanın.
Lucretiel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.