Chuyển hướng stdout sang một tệp trong Python?


314

Làm cách nào để chuyển hướng thiết bị xuất chuẩn đến một tệp tùy ý trong Python?

Khi tập lệnh Python chạy dài (ví dụ: ứng dụng web) được khởi động từ trong phiên ssh và được sao lưu và phiên ssh bị đóng, ứng dụng sẽ tăng IOError và thất bại ngay khi nó cố ghi vào thiết bị xuất chuẩn. Tôi cần tìm một cách để làm cho ứng dụng và mô-đun xuất ra một tệp chứ không phải là thiết bị xuất chuẩn để tránh thất bại do IOError. Hiện tại, tôi sử dụng nohup để chuyển hướng đầu ra sang một tệp và điều đó đã hoàn thành công việc, nhưng tôi tự hỏi liệu có cách nào để làm điều đó mà không sử dụng nohup, vì tò mò.

Tôi đã thử sys.stdout = open('somefile', 'w'), nhưng điều này dường như không ngăn được một số mô-đun bên ngoài vẫn xuất ra thiết bị đầu cuối (hoặc có thể sys.stdout = ...dòng này không kích hoạt gì cả). Tôi biết nó nên hoạt động từ các tập lệnh đơn giản hơn mà tôi đã thử nghiệm, nhưng tôi cũng chưa có thời gian để thử nghiệm trên một ứng dụng web.


8
Đó không thực sự là một con trăn, đó là một chức năng vỏ. Chỉ cần chạy kịch bản của bạn nhưscript.p > file
Falmarri

Tôi hiện đang giải quyết vấn đề bằng cách sử dụng nohup, nhưng tôi nghĩ có thể có một cái gì đó thông minh hơn ...

1
@foxbunny: nohup? Tại sao đơn giản someprocess | python script.py? Tại sao liên quan nohup?
S.Lott

3
Viết lại các printcâu lệnh để áp dụng loggingmô-đun từ stdlib. Sau đó, bạn có thể chuyển hướng đầu ra ở mọi nơi, có quyền kiểm soát số lượng đầu ra bạn muốn, v.v. Trong hầu hết các trường hợp, mã sản xuất không nên printnhưng log.
erikbwork

2
Có lẽ một giải pháp tốt hơn cho vấn đề này là lệnh màn hình, nó sẽ lưu phiên bash của bạn và cho phép bạn truy cập nó từ các lần chạy khác nhau.
Ryan Amos

Câu trả lời:


403

Nếu bạn muốn thực hiện chuyển hướng trong tập lệnh Python, việc đặt sys.stdoutthành một đối tượng tệp thực hiện thủ thuật:

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

Một phương pháp phổ biến hơn nhiều là sử dụng chuyển hướng shell khi thực thi (tương tự trên Windows và Linux):

$ python foo.py > file


7
Nó không hoạt động với from sys import stdout, có thể vì nó tạo ra một bản sao cục bộ. Ngoài ra, bạn có thể sử dụng nó với with, ví dụ with open('file', 'w') as sys.stdout: functionThatPrints(). Bây giờ bạn có thể thực hiện functionThatPrints()bằng cách sử dụng các printcâu lệnh bình thường .
mgold

41
Tốt nhất là giữ một bản sao cục bộ, stdout = sys.stdoutđể bạn có thể đặt lại khi hoàn thành sys.stdout = stdout. Theo cách đó, nếu bạn được gọi từ một chức năng sử dụng, printbạn không làm hỏng chúng.
mgold

4
@Jan: buffering=0vô hiệu hóa bộ đệm (nó có thể ảnh hưởng tiêu cực đến hiệu suất (10 - 100 lần)). buffering=1cho phép đệm dòng để bạn có thể sử dụng tail -fcho đầu ra hướng dòng.
jfs

41
@mgold hoặc bạn có thể sử dụng sys.stdout = sys.__stdout__để lấy lại.
clemtoy

176

contextlib.redirect_stdout()chức năng trong Python 3.4:

from contextlib import redirect_stdout

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

Nó tương tự như:

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

có thể được sử dụng trên các phiên bản Python trước đó. Phiên bản thứ hai không thể tái sử dụng . Nó có thể được thực hiện một nếu muốn.

Nó không chuyển hướng thiết bị xuất chuẩn ở cấp độ mô tả tệp, ví dụ:

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''echo this also is not redirected'không được chuyển hướng đến output.txttập tin.

Để chuyển hướng ở cấp mô tả tệp, os.dup2()có thể được sử dụng:

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

Ví dụ tương tự hoạt động ngay bây giờ nếu stdout_redirected()được sử dụng thay vì 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')

Đầu ra mà trước đây đã được in trên thiết bị xuất chuẩn bây giờ sẽ output.txtmiễn là trình stdout_redirected()quản lý bối cảnh hoạt động.

Lưu ý: stdout.flush()không xóa bộ đệm C stdio trên Python 3 trong đó I / O được triển khai trực tiếp trên các cuộc gọi read()/ write()hệ thống. Để xóa tất cả các luồng đầu ra C stdio đang mở, bạn có thể gọi libc.fflush(None)một cách rõ ràng nếu một số tiện ích mở rộng C sử dụng I / O dựa trên 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

Bạn có thể sử dụng stdouttham số để chuyển hướng các luồng khác, không chỉ sys.stdoutví dụ: để hợp nhất sys.stderrsys.stdout:

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

Thí dụ:

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)

Lưu ý: stdout_redirected()trộn I / O được đệm ( sys.stdoutthường) và I / O không có bộ đệm (thao tác trên mô tả tệp trực tiếp). Coi chừng, có thể có vấn đề đệm .

Để trả lời, chỉnh sửa của bạn: bạn có thể sử dụng python-daemonđể tạo nền cho tập lệnh của mình và sử dụng loggingmô-đun (như @ erikb85 đã đề xuất ) thay vì các printcâu lệnh và chỉ chuyển hướng thiết bị xuất chuẩn cho tập lệnh Python chạy dài mà bạn đang sử dụng nohupbây giờ.


3
stdout_redirectedlà hữu ích. Xin lưu ý rằng điều này không hoạt động trong các doctest, vì SpoofOutdoctest xử lý đặc biệt sử dụng để thay thế sys.stdoutkhông có filenothuộc tính.
Chris Johnson

@ChrisJohnson: Nếu nó không tăng ValueError("Expected a file (`.fileno()`) or a file descriptor")thì đó là một lỗi. Bạn có chắc là nó không nuôi nó không?
jfs

Nó gây ra lỗi đó, đó là điều khiến nó không thể sử dụng được trong một doctest. Để sử dụng chức năng của bạn trong doctest, có vẻ cần phải chỉ định doctest.sys.__stdout__nơi chúng ta thường sử dụng sys.stdout. Đây không phải là vấn đề với chức năng của bạn, chỉ là một chỗ ở cần thiết cho doctest vì nó thay thế thiết bị xuất chuẩn bằng một đối tượng không có tất cả các thuộc tính mà một tệp thực sự sẽ có.
Chris Johnson

stdout_redirected()stdouttham số, bạn có thể đặt nó thành sys.__stdout__nếu bạn muốn chuyển hướng thiết bị xuất chuẩn python ban đầu (cần có giá trị .fileno()trong hầu hết các trường hợp). Nó không làm gì cho hiện tại sys.stdoutnếu chúng khác nhau. Đừng sử dụng doctest.sys; nó có sẵn một cách tình cờ
jfs

Điều này thực sự hoạt động tốt, tức là chuyển hướng stdout và stderr sang fd: with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
neok

91

bạn có thể thử điều này tốt hơn nhiều

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

Bất kỳ đề xuất cho đường ống đến loggerhoặc syslog?
DSummersl

Nếu bạn muốn chỉnh sửa một tập tin thì điều này không hữu ích lắm. Dù sao +1 cho thủ thuật hay
aIKid 20/03/2016

10
Điều này sẽ có hậu quả đối với mã giả sử sys.stdout là một đối tượng tệp đầy đủ với các phương thức như fileno () (bao gồm mã trong thư viện chuẩn python). Tôi sẽ thêm một phương thức __getattr __ (self, attr) vào phương thức trì hoãn tra cứu thuộc tính thành self.terminal. def __getattr__(self, attr): return getattr(self.terminal, attr)
peabody

4
Bạn phải thêm def flush(self):phương thức vào lớp Logger.
loretoparisi

1
@loretoparisi nhưng những gì thực sự đi trong phương pháp mà bạn tạo ra?
elkshadow5

28

Các câu trả lời khác không bao gồm trường hợp bạn muốn các quy trình rẽ nhánh chia sẻ thiết bị xuất chuẩn mới của mình.

Để làm việc đó:

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
người ta cần thay thế thuộc tính 'w' bằng, os.O_WRONLY | os.O_CREATE ... không thể gửi chuỗi vào các lệnh "os"!
Ch'marr

3
Chèn a sys.stdout.flush()trước close(1)câu lệnh để đảm bảo 'file'tệp chuyển hướng nhận được đầu ra. Ngoài ra, bạn có thể sử dụng một tempfile.mkstemp()tập tin thay cho 'file'. Và hãy cẩn thận, bạn không có các luồng khác đang chạy có thể đánh cắp tệp xử lý tệp đầu tiên của os sau os.close(1)nhưng trước khi 'file'mở để sử dụng tay cầm.
Alex Robinson

2
os.O_WRONLY của nó | os.O_CREAT ... không có E trên đó.
Jeff Sheffield


@ Ch'marr Đó là O_CREAT, không phải O_CREATE.
quant_dev

28

Trích dẫn từ PEP 343 - Câu lệnh "với" (câu lệnh nhập thêm):

Chuyển hướng stdout tạm thời:

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

Được sử dụng như sau:

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

Tất nhiên, điều này không an toàn cho chủ đề, nhưng cả hai đều không thực hiện điệu nhảy này bằng tay. Trong các chương trình đơn luồng (ví dụ trong tập lệnh), đây là cách làm phổ biến.


1
+1. Lưu ý: nó không hoạt động cho các quy trình con, vd os.system('echo not redirected'). Câu trả lời của tôi cho thấy cách chuyển hướng đầu ra như vậy
jfs

bắt đầu từ Python 3,4 có redirect_stdouttrongcontextlib
Walter Tross


3

Đây là một biến thể của câu trả lời Yuda Prawira :

  • thực hiện flush()và tất cả các thuộc tính tệp
  • viết nó như một bối cảnh
  • chụp stderrcũng

.

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

Dựa trên câu trả lời này: https://stackoverflow.com/a/5916874/1060344 , đây là một cách khác để tôi tìm ra cái mà tôi sử dụng trong một trong các dự án của mình. Đối với bất cứ điều gì bạn thay thế sys.stderrhoặc sys.stdoutbằng, bạn phải đảm bảo rằng sự thay thế tuân thủ filegiao diện, đặc biệt nếu đây là việc bạn đang làm vì stderr / stdout được sử dụng trong một số thư viện khác không thuộc quyền kiểm soát của bạn. Thư viện đó có thể đang sử dụng các phương thức khác của đối tượng tệp.

Hãy xem cách này để tôi vẫn để mọi thứ thực hiện stderr / stdout (hoặc bất kỳ tệp nào cho vấn đề đó) và cũng gửi tin nhắn đến tệp nhật ký bằng cách sử dụng tiện ích ghi nhật ký của Python (nhưng bạn thực sự có thể làm bất cứ điều gì với điều này):

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

Bạn cần một bộ ghép kênh đầu cuối như màn hình tmux hoặc GNU

Tôi ngạc nhiên khi một nhận xét nhỏ của Ryan Amos 'cho câu hỏi ban đầu là đề cập duy nhất về một giải pháp được ưu tiên hơn nhiều so với tất cả những người khác được cung cấp, bất kể thủ thuật trăn có thể thông minh đến mức nào và họ đã nhận được bao nhiêu lượt ủng hộ. Theo nhận xét của Ryan, tmux là một lựa chọn tốt cho màn hình GNU.

Nhưng nguyên tắc là như nhau: nếu bạn thấy mình muốn rời bỏ một công việc cuối cùng trong khi bạn đăng xuất, hãy đến quán cà phê để ăn bánh sandwich, vào phòng tắm, về nhà (vv) và sau đó, kết nối lại với bạn phiên terminal từ bất cứ nơi nào hoặc bất kỳ máy tính như thể bạn không bao giờ được đi, multiplexers thiết bị đầu cuối là các câu trả lời. Hãy nghĩ về họ như VNC hoặc máy tính để bàn từ xa cho các phiên cuối. Bất cứ điều gì khác là một cách giải quyết. Như một phần thưởng, khi ông chủ và / hoặc đối tác đến và bạn vô tình ctrl-w / cmd-w cửa sổ thiết bị đầu cuối của bạn thay vì cửa sổ trình duyệt với nội dung tinh ranh của nó, bạn sẽ không mất 18 giờ xử lý cuối cùng !


4
trong khi đó là một câu trả lời tốt cho phần câu hỏi xuất hiện sau khi chỉnh sửa; nó không trả lời câu hỏi trong tiêu đề (hầu hết mọi người đến đây từ google cho tiêu đề)
jfs

0

Các chương trình được viết bằng các ngôn ngữ khác (ví dụ C) phải thực hiện phép thuật đặc biệt (được gọi là gấp đôi) để tách khỏi thiết bị đầu cuối (và để ngăn chặn các quá trình zombie). Vì vậy, tôi nghĩ giải pháp tốt nhất là thi đua với họ.

Điểm cộng của việc thực hiện lại chương trình của bạn là, bạn có thể chọn chuyển hướng trên dòng lệnh, vd /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

Xem bài đăng này để biết thêm: Lý do để thực hiện một ngã ba đôi khi tạo một daemon là gì?


Ơ ... không thể nói tôi là một fan hâm mộ của các quy trình quản lý việc rèn đôi của riêng họ. Nó là một thành ngữ rất phổ biến và rất dễ viết mã sai nếu bạn không cẩn thận. Tốt hơn là viết quy trình của bạn để chạy ở nền trước và sử dụng trình quản lý tác vụ nền hệ thống ( systemd, upstart) hoặc tiện ích khác ( daemon(1)) để xử lý bản tóm tắt forking.
Lucretiel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.