Implement "tail -f" in C++
Asked Answered
C

6

19

I want to create a small code in C++ with the same functionality as "tail-f": watch for new lines in a text file and show them in the standard output.

The idea is to have a thread that monitors the file

Is there an easy way to do it without opening and closing the file each time?

Caddy answered 23/4, 2010 at 7:48 Comment(6)
You might have a hard time doing this in pure C++. You will have to use some platform-specific API. (For starters, I don't think you can open a file in C++ non-exclusively.)Hardtop
@Hardtop I don't think the C++ standard has anything to say on exclusiveness.Infrared
Is there some reason you can't just use tail -f?Brobdingnagian
@Neil: No, it hasn't. Which is why the fstream implementations I know seem to open the file exclusively.Hardtop
@Hardtop - you can open a file handle with a variety of sharing modes in C++.Clayberg
@Konrad: ios_base::openmode doesn't have anything like it. What am I missing?Hardtop
D
16

Have a look at inotify on Linux or kqueue on Mac OS.

Inotify is Linux kernel subsystem that allows you to subscribe for events on files and it will report to your application when the even happened on your file.

Disembogue answered 23/4, 2010 at 8:1 Comment(1)
Windows has an equivalent API for sending notifications when a file changes.Impend
I
15

Just keep reading the file. If the read fails, do nothing. There's no need to repeatedly open and close it. However, you will find it much more efficient to use operating system specific features to monitor the file, should your OS provide them.

Infrared answered 23/4, 2010 at 7:51 Comment(1)
+1: Trying to read to the end of the file from where you've got to so far (for a reasonable length file) once a second is pretty cheap in practice. You just read until you've got to the end, then sleep for a second and try the read again. (If you're on Windows, take care to open with the right sharing flags so that you don't lock out other writers. That probably means using the native IO calls rather than the C++ standard ones...)Uranian
V
2

Same as in https://mcmap.net/q/636439/-implement-quot-tail-f-quot-in-c except that the code below uses getline instead of getc and doesn't skip new lines

#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);
  } 

} 
Vestigial answered 18/9, 2013 at 19:38 Comment(1)
filesize < last_position doesn't necessarily mean a new file started. It could also mean reached the end of fileChrestomathy
D
1

I read this in one of Perl manuals, but it is easily translated into standard C, which, in turn, can be translated to 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.

As far as I remember, fseek is called iostream::seekg. So you should basically do the same: seek to the end of the file, then sleep and seek again with ios_base::cur flag to update end-of-file and read some more data.

Instead of sleeping, you may use inotify, as suggested in the other answer, to sleep (block while reading from an emulated file, actually) exactly until the file is updated/closed. But that's Linux-specific, and is not standard C++.

Diploma answered 23/4, 2010 at 8:44 Comment(2)
love the "we hope" closing. Sticks well together with "this is weird, but it's good because it's weird" and the many other self-bashing realizations that are so typical of perl...Opal
@Stefano - well, it's good here because it relates to Perl's implementation of linewise file reading (<FILE>), and is not about how fseek works. I hope.Diploma
T
1

I needed to implement this too, I just wrote a quick hack in standard C++. The hack searches for the last 0x0A (linefeed character) in a file and outputs all data following that linefeed when the last linefeed value becomes a larger value. The code is here:

#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);
  }

}
Trial answered 22/9, 2011 at 11:32 Comment(0)
H
0
#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;
}
Heft answered 13/6, 2019 at 8:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.