【问题标题】:Implement "tail -f" in C++在 C++ 中实现“tail -f”
【发布时间】:2023-03-25 04:43:01
【问题描述】:

我想在 C++ 中创建一个与“tail-f”功能相同的小代码:观察文本文件中的新行并将它们显示在标准输出中。

这个想法是有一个监视文件的线程

有没有一种简单的方法可以不用每次都打开和关闭文件?

【问题讨论】:

  • 在纯 C++ 中你可能很难做到这一点。您将不得不使用一些特定于平台的 API。 (对于初学者,我认为你不能非独占地用 C++ 打开文件。)
  • @sbi 我不认为 C++ 标准对排他性有什么要说的。
  • 有什么原因你不能只使用 tail -f 吗?
  • @Neil:不,它没有。这就是为什么我知道的 fstream 实现似乎只打开文件的原因。
  • @sbi - 您可以在 C++ 中打开具有多种共享模式的文件句柄。

标签: c++ tail


【解决方案1】:

继续阅读文件。如果读取失败,什么也不做。无需反复打开和关闭它。但是,如果您的操作系统提供这些功能,您会发现使用操作系统特定的功能来监控文件会更有效。

【讨论】:

  • +1:在实践中,每秒一次尝试从你已经到达的位置(对于合理长度的文件)读取到文件末尾是相当便宜的。你只是读到最后,然后睡一秒钟,然后再读一次。 (如果您在 Windows 上,请注意使用正确的共享标志打开,这样您就不会锁定其他编写者。这可能意味着使用本机 IO 调用而不是 C++ 标准调用......)
【解决方案2】:

查看 Linux 上的 inotify 或 Mac OS 上的 kqueue。

Inotify 是 Linux 内核子系统,它允许您订阅文件上的事件,它会在文件上发生事件时向您的应用程序报告。

【讨论】:

  • Windows 有一个等效的 API 用于在文件更改时发送通知。
【解决方案3】:

https://stackoverflow.com/a/7514051/44729 相同,只是下面的代码使用 getline 而不是 getc 并且不跳过新行

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>

using namespace std;

static int last_position=0;
// read file untill new line
// save position

int find_new_text(ifstream &infile) {

   infile.seekg(0,ios::end);
   int filesize = infile.tellg();

   // check if the new file started
   if(filesize < last_position){
      last_position=0;
   }  
   // read file from last position  untill new line is found 

   for(int n=last_position;n<filesize;n++) {

      infile.seekg( last_position,ios::beg);
      char  test[256]; 
      infile.getline(test, 256);
      last_position = infile.tellg();
      cout << "Char: "  << test <<"Last position " << last_position<<  endl;
      // end of file 
      if(filesize == last_position){
        return filesize;
      } 

  }

  return 0;
}


int main() {

  for(;;) {
    std::ifstream infile("filename");
    int current_position = find_new_text(infile);
    sleep(1);
  } 

} 

【讨论】:

  • filesize
【解决方案4】:

我在one of Perl manuals 中读到了这个,但它很容易翻译成标准C,而标准C 又可以翻译成istreams。

   seek FILEHANDLE,POSITION,WHENCE
      Sets FILEHANDLE's position, just like the "fseek" call of
      "stdio".  
       <...>
       A WHENCE of 1 ("SEEK_CUR") is useful for not moving the file 
       position:

           seek(TEST,0,1);

       This is also useful for applications emulating "tail -f".  Once
       you hit EOF on your read, and then sleep for a while, you might
       have to stick in a seek() to reset things.  The "seek" doesn't
       change the current position, but it does clear the end-of-file
       condition on the handle, so that the next "<FILE>" makes Perl
       try again to read something.  We hope.

据我所知,fseek 被称为iostream::seekg。所以你应该基本上做同样的事情:寻找到文件的末尾,然后休眠并使用ios_base::cur标志再次寻找以更新文件结尾并读取更多数据。

您可以使用 inotify 代替 sleeping,正如 the other answer 中所建议的那样,在文件更新/关闭之前完全休眠(实际上是在从模拟文件中读取时阻塞)。但这是特定于 Linux 的,不是标准的 C++。

【讨论】:

  • 喜欢“我们希望”的结尾。与“这很奇怪,但它很好,因为它很奇怪”以及许多其他典型的 perl 自我抨击实现很好地结合在一起......
  • @Stefano - 好吧,这很好,因为它与 Perl 的逐行文件读取 (&lt;FILE&gt;) 的实现有关,而不是关于 fseek 的工作原理。我希望。
【解决方案5】:

我也需要实现这一点,我刚刚用标准 C++ 编写了一个快速 hack。 hack 搜索文件中的最后一个 0x0A(换行符),并在最后一个换行值变为较大值时输出该换行符之后的所有数据。代码在这里:

#include <iostream>
#include <string>
#include <fstream>

using namespace std;


int find_last_linefeed(ifstream &infile) {

  infile.seekg(0,ios::end);
  int filesize = infile.tellg();

  for(int n=1;n<filesize;n++) {
    infile.seekg(filesize-n-1,ios::beg);

    char c;
    infile.get(c);

    if(c == 0x0A) return infile.tellg();
  }
}


int main() {


  int last_position=-1;
  for(;;) {

    ifstream infile("testfile");
    int position = find_last_linefeed(infile);

    if(position > last_position) {
      infile.seekg(position,ios::beg);
      string in;
      infile >> in;
      cout << in << endl;
    }
    last_position=position;

    sleep(1);
  }

}

【讨论】:

    【解决方案6】:
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <list>
    #include <sys/stat.h> 
    #include <stdlib.h>
    
    #define debug 0
    
    class MyTail
    {
    private:
    std::list<std::string> mLastNLine;
    const int mNoOfLines;
    std::ifstream mIn;
    
    public:
    
    explicit MyTail(int pNoOfLines):mNoOfLines(pNoOfLines) {}
    
    const int getNoOfLines() {return mNoOfLines; }
    
    void getLastNLines();
    
    void printLastNLines();
    
    void tailF(const char* filename);
    
    };
    
    void MyTail::getLastNLines() 
    {
        if (debug) std::cout << "In: getLastNLines()" << std::endl;
        mIn.seekg(-1,std::ios::end);
        int pos=mIn.tellg();
        int count = 1;
    
        //Get file pointer to point to bottom up mNoOfLines.
        for(int i=0;i<pos;i++)
        {
            if (mIn.get() == '\n')
                if (count++ > mNoOfLines)
                    break;
            mIn.seekg(-2,std::ios::cur);
        }
    
        //Start copying bottom mNoOfLines string into list to avoid I/O calls to print lines
        std::string line;
        while(getline(mIn,line)) {
            int data_Size = mLastNLine.size();
            if(data_Size >= mNoOfLines) {
                mLastNLine.pop_front();
            }
            mLastNLine.push_back(line);
        }
    
        if (debug) std::cout << "Out: getLastNLines()" << std::endl;
    }
    
    void MyTail::printLastNLines()
    {    
         for (std::list<std::string>::iterator i = mLastNLine.begin();  i !=         mLastNLine.end(); ++i)
         std::cout << *i << std::endl;
    }
    
    void MyTail::tailF(const char* filename)
    {
        if (debug) std::cout << "In: TailF()" << std::endl;
    
        int date = 0;
        while (true) {
            struct stat st;
            stat (filename, &st);
            int newdate = st.st_mtime;
            if (newdate != date){
                system("@cls||clear");
                std::cout << "Print last " << getNoOfLines() << " Lines: \n";
                mIn.open(filename);
                date = newdate;
                getLastNLines();
                mIn.close();
                printLastNLines();
            }
        }
        if (debug) std::cout << "Out: TailF()" << std::endl;        
    }
    
    int main(int argc, char **argv)
    {
        if(argc==1) {
            std::cout << "No Extra Command Line Argument Passed Other Than Program Name\n"; 
            return 0;
        }
    
        if(argc>=2) {
            MyTail t1(10);
            t1.tailF(argv[1]);
        }
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      • 2015-08-31
      相关资源
      最近更新 更多