【问题标题】:Reading from a frequently updated file从经常更新的文件中读取
【发布时间】:2018-09-15 00:33:31
【问题描述】:

我目前正在 Linux 系统上用 python 编写程序。目标是读取日志文件并在找到特定字符串时执行 bash 命令。日志文件不断被另一个程序写入。

我的问题:如果我使用 open() 方法打开文件,我的 Python 文件对象会随着其他程序写入实际文件而更新,还是我必须重新打开文件每隔一段时间?

更新:感谢到目前为止的回答。我也许应该提到该文件是由 Java EE 应用程序写入的,因此我无法控制何时将数据写入其中。我目前有一个程序,它每 10 秒重新打开一次文件,并尝试从文件中上次读取的字节位置读取。目前它只是打印出返回的字符串。我希望文件不需要重新打开,但读取命令会以某种方式访问​​ Java 应用写入文件的数据。

#!/usr/bin/python
import time

fileBytePos = 0
while True:
    inFile = open('./server.log','r')
    inFile.seek(fileBytePos)
    data = inFile.read()
    print data
    fileBytePos = inFile.tell()
    print fileBytePos
    inFile.close()
    time.sleep(10)

感谢有关 pyinotify 和生成器的提示。我将看看这些以获得更好的解决方案。

【问题讨论】:

    标签: python file-io generator fopen


    【解决方案1】:

    这取决于你想对文件做什么。这有两个潜在的用例:

    1. 从持续更新的文件(例如日志文件)中读取附加内容。
    2. 从被连续覆盖的文件中读取内容(例如 *nix 系统中的网络统计文件)

    由于其他人已经详细回答了如何解决方案#1,我想帮助那些需要方案#2 的人。基本上,您需要在调用read() n+1th 时间之前使用seek(0)(或您要读取的任何位置)将文件指针重置为0。

    您的代码可能看起来有点像下面的函数。

    def generate_network_statistics(iface='wlan0'):
        with open('/sys/class/net/' + iface + '/statistics/' + 'rx' + '_bytes', 'r') as rx:
            with open('/sys/class/net/' + iface + '/statistics/' + 'tx' + '_bytes', 'r') as tx:
                with open('/proc/uptime', 'r') as uptime:
                    while True:
                        receive = int(rx.read())
                        rx.seek(0)
                        transmit = int(tx.read())
                        tx.seek(0)
                        uptime_seconds = int(uptime.read())
                        uptime.seek(0)
                        print("Receive: %i, Transmit: %i" % (receive, transmit))
                        time.sleep(1)
    

    【讨论】:

      【解决方案2】:

      我有一个类似的用例,我为它写了下面的sn-p。 虽然有些人可能会争辩说这不是最理想的方式,但这可以完成工作并且看起来很容易理解。

      def reading_log_files(filename):
          with open(filename, "r") as f:
              data = f.read().splitlines()
          return data
      
      
      def log_generator(filename, period=1):
          data = reading_log_files(filename)
          while True:
              time.sleep(period)
              new_data = reading_log_files(filename)
              yield new_data[len(data):]
              data = new_data
      
      
      if __name__ == '__main__':
          x = log_generator(</path/to/log/file.log>)
          for lines in x:
              print(lines)
              # lines will be a list of new lines added at the end
      

      希望你觉得这很有用

      【讨论】:

      • 这对我的用例很有帮助。谢谢:)
      【解决方案3】:

      我建议查看 David Beazley 的 Generator Tricks for Python,尤其是第 5 部分:处理无限数据。它将实时处理与tail -f logfile 命令等效的 Python。

      # follow.py
      #
      # Follow a file like tail -f.
      
      import time
      def follow(thefile):
          thefile.seek(0,2)
          while True:
              line = thefile.readline()
              if not line:
                  time.sleep(0.1)
                  continue
              yield line
      
      if __name__ == '__main__':
          logfile = open("run/foo/access-log","r")
          loglines = follow(logfile)
          for line in loglines:
              print line,
      

      【讨论】:

      • 如果答案包含关于 OP 代码的代码示例,我会投票赞成。
      • @Chiel92:添加了来自 David Beazley 网站的代码示例
      • 这个答案是错误的,如果作者在两个单独的块中写入一行,则 readline 将返回两次。但你真的只想返回一行。
      • thefile.seek(0,2) 是做什么的?
      • @RylanSchaeffer 0 是偏移量,2 表示相对于文件末尾的查找。
      【解决方案4】:

      这是Jeff Bauer 答案的略微修改版本,它可以防止文件截断。如果您的文件正在由logrotate 处理,则非常有用。

      import os
      import time
      
      def follow(name):
          current = open(name, "r")
          curino = os.fstat(current.fileno()).st_ino
          while True:
              while True:
                  line = current.readline()
                  if not line:
                      break
                  yield line
      
              try:
                  if os.stat(name).st_ino != curino:
                      new = open(name, "r")
                      current.close()
                      current = new
                      curino = os.fstat(current.fileno()).st_ino
                      continue
              except IOError:
                  pass
              time.sleep(1)
      
      
      if __name__ == '__main__':
          fname = "test.log"
          for l in follow(fname):
              print "LINE: {}".format(l)
      

      【讨论】:

      • 看到while True: while True:很害怕
      【解决方案5】:

      “一个互动会话值 1000 字”

      >>> f1 = open("bla.txt", "wt")
      >>> f2 = open("bla.txt", "rt")
      >>> f1.write("bleh")
      >>> f2.read()
      ''
      >>> f1.flush()
      >>> f2.read()
      'bleh'
      >>> f1.write("blargh")
      >>> f1.flush()
      >>> f2.read()
      'blargh'
      

      换句话说 - 是的,一个“打开”就可以了。

      【讨论】:

        【解决方案6】:

        如果您有读取文件的代码在 while 循环中运行:

        f = open('/tmp/workfile', 'r')
        while(1):
            line = f.readline()
            if line.find("ONE") != -1:
                print "Got it"
        

        并且您正在从另一个程序写入同一个文件(以附加模式)。只要在文件中附加“ONE”,您就会得到打印。你可以采取任何你想采取的行动。简而言之,您不必定期重新打开文件。

        >>> f = open('/tmp/workfile', 'a')
        >>> f.write("One\n")
        >>> f.close()
        >>> f = open('/tmp/workfile', 'a')
        >>> f.write("ONE\n")
        >>> f.close()
        

        【讨论】:

        • 这个答案也是错误的,写入可能会分成'ON'和'E\n',这将导致两行都不匹配。
        【解决方案7】:

        由于您的目标是 Linux 系统,因此您可以使用 pyinotify 在文件更改时通知您。

        还有this 技巧,它可能对你有用。它使用file.seek 来做tail -f 所做的事情。

        【讨论】:

          【解决方案8】:

          我不是这里的专家,但我认为您必须使用某种观察者模式来被动地观察文件,然后在发生更改时触发重新打开文件的事件。至于如何实际实现,我不知道。

          我认为 open() 不会像你建议的那样实时打开文件。

          【讨论】:

            猜你喜欢
            • 2016-10-24
            • 1970-01-01
            • 1970-01-01
            • 2013-05-19
            • 2013-06-23
            • 2014-09-30
            • 2021-02-14
            相关资源
            最近更新 更多