Внедрить "tail -f" в С++

Я хочу создать небольшой код на С++ с той же функциональностью, что и "tail-f": следить за новыми строками в текстовом файле и показывать их в стандартном выводе.

Идея состоит в том, чтобы иметь поток, который контролирует файл

Есть ли простой способ сделать это, не открывая и не закрывая файл каждый раз?

Ответы

Ответ 1

Просто продолжайте читать файл. Если чтение не выполняется, ничего не делайте. Нет необходимости многократно открывать и закрывать его. Тем не менее, вы обнаружите, что гораздо эффективнее использовать специфические для операционной системы функции для мониторинга файла, если ваша ОС предоставляет им.

Ответ 2

Посмотрите inotify на Linux или kqueue в Mac OS.

Inotify - подсистема ядра Linux, которая позволяет вам подписываться на события в файлах и сообщать о своем заявлении, когда это произошло даже ваш файл.

Ответ 3

То же, что и в fooobar.com/questions/363906/..., за исключением того, что приведенный ниже код использует 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);
  } 

} 

Ответ 4

Я читал это в одном из руководств Perl, но он легко переводится в стандарт C, который, в свою очередь, можно перевести на istream с.

   seek FILEHANDLE,POSITION,WHENCE
      Sets FILEHANDLE 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 для обновления конца файла и чтения еще нескольких данных.

Вместо sleep ing вы можете использовать inotify, как предложено в другом ответе, чтобы спать (на самом деле блокировать при чтении из эмулируемого файла) пока файл не будет обновлен/закрыт. Но этот Linux-специфический и не является стандартным С++.

Ответ 5

Мне тоже нужно было реализовать это, я просто написал быстрый хак в стандартном С++. 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;
}