Implement "tail -f" in C++
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 moni开发者_Go百科tors the file
Is there an easy way to do it without opening and closing the file each time?
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.
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.
Same as in https://stackoverflow.com/a/7514051/44729 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);
}
}
I read this in one of Perl manuals, but it is easily translated into standard C, which, in turn, can be translated to istream
s.
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 sleep
ing, 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++.
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);
}
}
#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;
}
精彩评论