【问题标题】:How to redirect stdout to both file and console with scripting?如何使用脚本将标准输出重定向到文件和控制台?
【发布时间】:2013-02-01 03:21:44
【问题描述】:

我想运行一个 python 脚本并在文本文件中捕获输出,并希望在控制台上显示。

我想将它指定为 python 脚本本身的属性。不要每次都在命令提示符下使用命令echo "hello world" | tee test.txt

在我尝试过的脚本中:

sys.stdout = open('log.txt','w')

但这不会在屏幕上显示标准输出。

我听说过日志记录模块,但我无法使用该模块来完成这项工作。

【问题讨论】:

    标签: python python-2.7


    【解决方案1】:

    您可以在执行 Python 文件时使用 shell 重定向:

    python foo_bar.py > file
    

    这会将打印在标准输出上的所有结果从 Python 源文件写入日志文件。

    或者如果你想从脚本中记录:

    import sys
    
    class Logger(object):
        def __init__(self):
            self.terminal = sys.stdout
            self.log = open("logfile.log", "a")
       
        def write(self, message):
            self.terminal.write(message)
            self.log.write(message)  
    
        def flush(self):
            # this flush method is needed for python 3 compatibility.
            # this handles the flush command by doing nothing.
            # you might want to specify some extra behavior here.
            pass    
    
    sys.stdout = Logger()
    

    现在你可以使用了:

    print "Hello"
    

    这会将“Hello”写入标准输出和日志文件。

    【讨论】:

    • 嗨,Amith,我不想使用它,因为它需要手动交互来执行此操作(> 文件)。有什么我可以在脚本中使用的东西吗?或者一旦执行完成,控制台上会出现什么,然后推送到文件??
    • @user2033758:一旦它退出你的程序并在执行完成后在控制台上,程序就无法再控制它了。
    • @user2033758:这个答案有两个建议,第二个不需要任何手动交互。您可以使用命令行,也可以在代码中使用该类。我对此进行了测试,该类将输出发送到控制台和文件,命令行上没有任何特殊参数。
    • 如何恢复正常的sys.stdout 行为?假设我想要这个,但只是在我的程序的启动阶段,然后过一段时间我想让程序运行,但不保存所有内容
    • @TokeFaurby你可以将之前的sys.stdout保存在Logger中,并创建一个函数将其恢复为原始状态。
    【解决方案2】:

    我可以同时将输出重定向到控制台和文本文件:

    te = open('log.txt','w')  # File where you need to keep the logs
    
    class Unbuffered:
    
       def __init__(self, stream):
    
           self.stream = stream
    
       def write(self, data):
    
           self.stream.write(data)
           self.stream.flush()
           te.write(data)    # Write the data of stdout here to a text file as well
    
    
    
    sys.stdout=Unbuffered(sys.stdout)
    

    【讨论】:

    • 我运行了一个 python 进程并且无法看到log.txt 文件中的数据,除非我终止该进程。看起来日志正在缓冲,然后在最后转储。
    • 你好@MukeshKumar,我也面临同样的问题。我在文件末尾添加了te.flush(),以强制它停止缓冲数据并写入。另外,我在删除数据时遇到了问题,因为它仍然在应用程序中打开,所以我添加了te.close()。第三个问题是,我在 Jupyter 中的单元一直在无限期地运行,直到我杀死内核。我通过捕获标准输出 (orig_stdout = sys.stdout) 的原始状态并在脚本末尾再次分配它 (sys.stdout = orig_stdout) 来解决它。在那之后,一切都运行得很好。
    • 嗨@ArnoldSouza 感谢您的回复。 te.flush() 是如何工作的?它什么时候将数据刷新到日志文件?它是基于时间还是缓冲区大小?你能分享你的代码吗,以防我遗漏了什么
    • 我在此堆栈中发布了代码作为答案。关于 obj.flush(),我相信函数 open 基于缓冲区大小工作。更多信息在这里:stackoverflow.com/questions/3167494/…
    • 嗨阿诺德,我想知道,如果我要停止流式捕获,是否在脚本末尾添加这些命令? te.flush\ t.close()\ sys.stdout = orig_stdout?我的 jupyter 仍在无限期运行。
    【解决方案3】:

    使用日志模块来调试和关注您的应用

    这是我如何设法记录到文件和控制台/标准输出

    import logging
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s - %(levelname)s - %(message)s',
                        filename='logs_file',
                        filemode='w')
    # Until here logs only to file: 'logs_file'
    
    # define a new Handler to log to console as well
    console = logging.StreamHandler()
    # optional, set the logging level
    console.setLevel(logging.INFO)
    # set a format which is the same for console use
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # tell the handler to use this format
    console.setFormatter(formatter)
    # add the handler to the root logger
    logging.getLogger('').addHandler(console)
    
    # Now, we can log to both ti file and console
    logging.info('Jackdaws love my big sphinx of quartz.')
    logging.info('Hello world')
    

    从源代码阅读: https://docs.python.org/2/howto/logging-cookbook.html

    【讨论】:

    • 复制粘贴别人自己的答案并不酷 ttps://stackoverflow.com/a/9321890/7646049 只需提供参考即可。
    • @EvgenyKuznetsov 我发布了一个指向 python 文档的链接,这是我从中复制的;您是否认为其他 stackoverflow 答案可能是从同一个地方复制的?
    【解决方案4】:

    基于Amith Koujalgi's answer,这是一个可用于日志记录的简单模块 -

    transcript.py:

    """
    Transcript - direct print output to a file, in addition to terminal.
    
    Usage:
        import transcript
        transcript.start('logfile.log')
        print("inside file")
        transcript.stop()
        print("outside file")
    """
    
    import sys
    
    class Transcript(object):
    
        def __init__(self, filename):
            self.terminal = sys.stdout
            self.logfile = open(filename, "a")
    
        def write(self, message):
            self.terminal.write(message)
            self.logfile.write(message)
    
        def flush(self):
            # this flush method is needed for python 3 compatibility.
            # this handles the flush command by doing nothing.
            # you might want to specify some extra behavior here.
            pass
    
    def start(filename):
        """Start transcript, appending print output to given filename"""
        sys.stdout = Transcript(filename)
    
    def stop():
        """Stop transcript and return print functionality to normal"""
        sys.stdout.logfile.close()
        sys.stdout = sys.stdout.terminal
    

    【讨论】:

      【解决方案5】:

      我设计了一个更简单的解决方案。只需定义一个将打印到文件或屏幕或两者的函数。在下面的示例中,我允许用户输入输出文件名作为参数,但这不是强制性的:

      OutputFile= args.Output_File
      OF = open(OutputFile, 'w')
      
      def printing(text):
          print text
          if args.Output_File:
              OF.write(text + "\n")
      

      在此之后,将一行打印到文件和/或屏幕所需的只是: 打印(Line_to_be_printed)

      【讨论】:

      • 使用自定义函数处理它的简单而绝妙的想法。
      • 这行得通。但是,请注意“打印”功能的输入“文本”必须是显式文本。它不支持 print 支持的任何其他参数。
      • 我支持非显式文本(如 datetime.datetime)的问题在于 write 函数。我只是用str(text) 包围了文本以转换为字符串。
      【解决方案6】:
      from IPython.utils.io import Tee
      from contextlib import closing
      
      print('This is not in the output file.')        
      
      with closing(Tee("outputfile.log", "w", channel="stdout")) as outputstream:
          print('This is written to the output file and the console.')
          # raise Exception('The file "outputfile.log" is closed anyway.')
      print('This is not written to the output file.')   
      
      # Output on console:
      # This is not in the output file.
      # This is written to the output file and the console.
      # This is not written to the output file.
      
      # Content of file outputfile.txt:
      # This is written to the output file and the console.
      

      IPython.utils.io 中的 Tee 类可以满足您的需求,但它缺少在 with 语句中调用它所需的 __enter____exit__ 方法。这些是contextlib.closing添加的。

      【讨论】:

        【解决方案7】:

        这是一个简单的上下文管理器,它打印到控制台并将相同的输出写入文件。它还会将任何异常写入文件。

        import traceback
        import sys
        
        # Context manager that copies stdout and any exceptions to a log file
        class Tee(object):
            def __init__(self, filename):
                self.file = open(filename, 'w')
                self.stdout = sys.stdout
        
            def __enter__(self):
                sys.stdout = self
        
            def __exit__(self, exc_type, exc_value, tb):
                sys.stdout = self.stdout
                if exc_type is not None:
                    self.file.write(traceback.format_exc())
                self.file.close()
        
            def write(self, data):
                self.file.write(data)
                self.stdout.write(data)
        
            def flush(self):
                self.file.flush()
                self.stdout.flush()
        

        使用上下文管理器:

        print("Print")
        with Tee('test.txt'):
            print("Print+Write")
            raise Exception("Test")
        print("Print")
        

        【讨论】:

        • 好答案!在 Python3 中,由于缺少“isatty”而出现错误。要修复它,请添加以下内容: def isatty(self): return False
        【解决方案8】:

        我在这里尝试了一些解决方案,但没有找到同时写入文件和控制台的解决方案。所以这就是我所做的(based on this answer)

        class Logger(object):
            def __init__(self):
                self.terminal = sys.stdout
        
            def write(self, message):
                with open ("logfile.log", "a", encoding = 'utf-8') as self.log:            
                    self.log.write(message)
                self.terminal.write(message)
        
            def flush(self):
                #this flush method is needed for python 3 compatibility.
                #this handles the flush command by doing nothing.
                #you might want to specify some extra behavior here.
                pass
        sys.stdout = Logger()   
        

        此解决方案使用更多的计算能力,但可靠地将所有数据从标准输出保存到记录器文件中,并且使用更少的内存。为了我的需要,我还在 self.log.write(message) 中添加了时间戳。效果很好。

        【讨论】:

        • 对于每个日志行,你打开一个文件,写一行,关闭文件......没有必要。
        【解决方案9】:

        这种方式在我的情况下非常有效。我只是根据这个线程中提供的其他代码添加了一些修改。

        import sys, os 
        
        orig_stdout = sys.stdout  # capture original state of stdout
        
        te = open('log.txt','w')  # File where you need to keep the logs
        
        class Unbuffered:
           def __init__(self, stream):
               self.stream = stream
        
           def write(self, data):
               self.stream.write(data)
               self.stream.flush()
               te.write(data)    # Write the data of stdout here to a text file as well
        
        sys.stdout=Unbuffered(sys.stdout)
        
        
        
        
        #######################################
        ##  Feel free to use print function  ##
        #######################################
        
        print("Here is an Example =)")
        
        #######################################
        ##  Feel free to use print function  ##
        #######################################
        
        
        
        
        # Stop capturing printouts of the application from Windows CMD
        sys.stdout = orig_stdout  # put back the original state of stdout
        te.flush()  # forces python to write to file
        te.close()  # closes the log file
        
        # read all lines at once and capture it to the variable named sys_prints
        with open('log.txt', 'r+') as file:
            sys_prints = file.readlines() 
        
        # erase the file contents of log file
        open('log.txt', 'w').close()
        

        【讨论】:

        • 这是迄今为止最好的答案。
        【解决方案10】:

        要将输出重定向到文件和终端而不修改 Python 脚本在外部的使用方式,您可以使用 pty.spawn(itself):

        #!/usr/bin/env python
        """Redirect stdout to a file and a terminal inside a script."""
        import os
        import pty
        import sys
        
        def main():
            print('put your code here')
        
        if __name__=="__main__":
            sentinel_option = '--dont-spawn'
            if sentinel_option not in sys.argv:
                # run itself copying output to the log file
                with open('script.log', 'wb') as log_file:
                    def read(fd):
                        data = os.read(fd, 1024)
                        log_file.write(data)
                        return data
        
                    argv = [sys.executable] + sys.argv + [sentinel_option]
                    rc = pty.spawn(argv, read)
            else:
                sys.argv.remove(sentinel_option)
                rc = main()
            sys.exit(rc)
        

        如果pty 模块不可用(在 Windows 上),那么您可以将其替换为更便携的teed_call() function,但它提供普通管道而不是伪终端——它可能会改变某些程序的行为。

        基于pty.spawnsubprocess.Popen 的解决方案与将sys.stdout 替换为类似文件的对象相比的优势在于,它们可以在文件描述符级别捕获输出,例如,如果脚本启动其他进程也可以在 stdout/stderr 上产生输出。请参阅我对相关问题的回答:Redirect stdout to a file in Python?

        【讨论】:

          【解决方案11】:

          基于Brian Burns edited answer,我创建了一个更容易调用的单个类:

          class Logger(object):
          
              """
              Class to log output of the command line to a log file
          
              Usage:
              log = Logger('logfile.log')
              print("inside file")
              log.stop()
              print("outside file")
              log.start()
              print("inside again")
              log.stop()
              """
          
              def __init__(self, filename):
                  self.filename = filename
          
              class Transcript:
                  def __init__(self, filename):
                      self.terminal = sys.stdout
                      self.log = open(filename, "a")
                  def __getattr__(self, attr):
                      return getattr(self.terminal, attr)
                  def write(self, message):
                      self.terminal.write(message)
                      self.log.write(message)
                  def flush(self):
                      pass
          
              def start(self):
                  sys.stdout = self.Transcript(self.filename)
          
              def stop(self):
                  sys.stdout.log.close()
                  sys.stdout = sys.stdout.terminal
          

          【讨论】:

            【解决方案12】:

            根据@Arnold Suiza 的回答,这里有一个函数,您可以在开始时运行一次,然后将所有内容立即打印到标准输出和文件:

            def print_to_file(filename):
                orig_stdout = sys.stdout  # capture original state of stdout
            
                class Unbuffered:
                    def __init__(self, filename):
                        self.stream = orig_stdout
                        self.te = open(filename,'w')  # File where you need to keep the logs
            
                    def write(self, data):
                        self.stream.write(data)
                        self.stream.flush()
                        self.te.write(data)    # Write the data of stdout here to a text file as well
                        self.te.flush()
            
                sys.stdout=Unbuffered(filename)
            

            现在只需在程序启动时运行 print_to_file('log.txt') 就可以了!

            【讨论】:

            • 我可以知道我们如何停止 juypter 中的进程吗?它始终处于处理 (*) 状态。
            【解决方案13】:

            我试过了:

            """
            Transcript - direct print output to a file, in addition to terminal.
            
            Usage:
                import transcript
                transcript.start('logfile.log')
                print("inside file")
                transcript.stop()
                print("outside file")
            """
            
            import sys
            
            class Transcript(object):
            
                def __init__(self, filename):
                    self.terminal = sys.stdout, sys.stderr
                    self.logfile = open(filename, "a")
            
                def write(self, message):
                    self.terminal.write(message)
                    self.logfile.write(message)
            
                def flush(self):
                    # this flush method is needed for python 3 compatibility.
                    # this handles the flush command by doing nothing.
                    # you might want to specify some extra behavior here.
                    pass
            
            def start(filename):
                """Start transcript, appending print output to given filename"""
                sys.stdout = Transcript(filename)
            
            def stop():
                """Stop transcript and return print functionality to normal"""
                sys.stdout.logfile.close()
                sys.stdout = sys.stdout.terminal
                sys.stderr = sys.stderr.terminal
            

            【讨论】:

              【解决方案14】:

              您可以使用带有 print rint 的“chevron”语法的 >> python 将输出重定向到文件,如docs中所示

              让我们看看,

              fp=open('test.log','a')   # take file  object reference 
              print >> fp , "hello world"            #use file object with in print statement.
              print >> fp , "every thing will redirect to file "
              fp.close()    #close the file 
              

              签出文件 test.log 你会得到数据 并在控制台上打印只需使用 plain print 语句。

              【讨论】:

              • >> 不是重定向运算符,而是移位运算符。请改一下。
              • 这在python2.7中有效。 >> 可以与 print 语句一起使用以将字符串重定向到给定的文件对象。请自行尝试我的解决方案来验证
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-11-17
              • 1970-01-01
              • 1970-01-01
              • 2019-03-15
              • 1970-01-01
              • 2013-12-07
              • 1970-01-01
              相关资源
              最近更新 更多