C ++ quick way to read only the last line of a text file?

I would like to read only the last line of the text file (I'm on UNIX, I can use Boost). All the methods that I know require scanning across the entire file to get the last line, which is ineffective at all. Is there an efficient way to get only the last line?

Also, I need it to be reliable enough for it to work, even if the text file is constantly being added to another process.

+8
source share
7 answers

Use seekg to go to the end of the file, then return it until you find the first new line. Below is an example of code from the top of the head using MSVC.

#include <iostream> #include <fstream> #include <sstream> using namespace std; int main() { string filename = "test.txt"; ifstream fin; fin.open(filename); if(fin.is_open()) { fin.seekg(-1,ios_base::end); // go to one spot before the EOF bool keepLooping = true; while(keepLooping) { char ch; fin.get(ch); // Get current byte data if((int)fin.tellg() <= 1) { // If the data was at or before the 0th byte fin.seekg(0); // The first line is the last line keepLooping = false; // So stop there } else if(ch == '\n') { // If the data was a newline keepLooping = false; // Stop at the current position. } else { // If the data was neither a newline nor at the 0 byte fin.seekg(-2,ios_base::cur); // Move to the front of that data, then to the front of the data before it } } string lastLine; getline(fin,lastLine); // Read the current line cout << "Result: " << lastLine << '\n'; // Display it fin.close(); } return 0; } 

And below is a test file. It succeeds in empty, single-line and multi-line data in a text file.

 This is the first line. Some stuff. Some stuff. Some stuff. This is the last line. 
+17
source

Go to the end and start reading blocks back until you find any criteria for the line. If the last block does not β€œend” with the line, you will probably also have to try and scan ahead (provided that a file is added in the active application).

+4
source

You can use seekg () to go to the end of the file and read backward, the pseudocode looks like this:

 ifstream fs fs.seekg(ios_base::end) bytecount = fs.tellg() index = 1 while true fs.seekg(bytecount - step * index, ios_base::beg) fs.read(buf, step) if endlinecharacter in buf get endlinecharacter index, said ei fs.seekg(bytecount - step*index + ei) fs.read(lastline, step*index - ei) break ++index 
+1
source

While derpface's answer is definitely correct, it often returns unexpected results. The reason for this is because, at least on my operating system (Mac OSX 10.9.5), many text editors terminate their files with an end-line character.

For example, when I open vim, type only one β€œa” character (no return) and save, now the file will contain (in hexadecimal format):

 61 0A 

Where 61 is the letter "a" and 0A is the end of line character.

This means that the derpface code will return an empty string for all files created by such a text editor.

Although I can, of course, imagine cases where a file ending with an "end line" should return an empty line, I think that ignoring the last character of the "end line" would be more appropriate when working with regular text files; if the file ends with the "end line" symbol, we will properly ignore it, and if the file does not end with the "end line" symbol, we do not need to check it.

My code to ignore the last character of the input file:

 #include <iostream> #include <string> #include <fstream> #include <iomanip> int main() { std::string result = ""; std::ifstream fin("test.txt"); if(fin.is_open()) { fin.seekg(0,std::ios_base::end); //Start at end of file char ch = ' '; //Init ch not equal to '\n' while(ch != '\n'){ fin.seekg(-2,std::ios_base::cur); //Two steps back, this means we //will NOT check the last character if((int)fin.tellg() <= 0){ //If passed the start of the file, fin.seekg(0); //this is the start of the line break; } fin.get(ch); //Check the next character } std::getline(fin,result); fin.close(); std::cout << "final line length: " << result.size() <<std::endl; std::cout << "final line character codes: "; for(size_t i =0; i<result.size(); i++){ std::cout << std::hex << (int)result[i] << " "; } std::cout << std::endl; std::cout << "final line: " << result <<std::endl; } return 0; } 

It will display:

 final line length: 1 final line character codes: 61 final line: a 

In the single file 'a'.

EDIT: line if((int)fin.tellg() <= 0){ really causes problems if the file is too large (> 2 GB) because tellg does not just return the number of characters from the beginning of the file (the tellg () function gives the wrong file size? ). It might be better to try separately to start the fin.tellg()==tellgValueForStartOfFile and for fin.tellg()==-1 errors. tellgValueForStartOfFile is probably 0, but the best way to make sure is probably the following:

 fin.seekg (0, is.beg); tellgValueForStartOfFile = fin.tellg(); 
+1
source

This was originally intended to read the last syslog entry. Given that the last character before EOF is '\n' , we look back for the next detection of '\n' , and then we store the string in the string.

 #include <fstream> #include <iostream> int main() { const std::string filename = "test.txt"; std::ifstream fs; fs.open(filename.c_str(), std::fstream::in); if(fs.is_open()) { //Got to the last character before EOF fs.seekg(-1, std::ios_base::end); if(fs.peek() == '\n') { //Start searching for \n occurrences fs.seekg(-1, std::ios_base::cur); int i = fs.tellg(); for(i;i > 0; i--) { if(fs.peek() == '\n') { //Found fs.get(); break; } //Move one character back fs.seekg(i, std::ios_base::beg); } } std::string lastline; getline(fs, lastline); std::cout << lastline << std::endl; } else { std::cout << "Could not find end line character" << std::endl; } return 0; } 
+1
source

I also struggled with the problem because I ran the uberwulu code and also got an empty line. Here is what I found. As an example, I use the following CSV file:

 date test1 test2 20140908 1 2 20140908 11 22 20140908 111 235 

To understand the commands in the code, pay attention to the following places and their corresponding symbols. (Loc, char): ... (63, '3'), (64, '5'), (65, -), (66, '\ n'), (EOF, -).

 #include<iostream> #include<string> #include<fstream> using namespace std; int main() { std::string line; std::ifstream infile; std::string filename = "C:/projects/MyC++Practice/Test/testInput.csv"; infile.open(filename); if(infile.is_open()) { char ch; infile.seekg(-1, std::ios::end); // move to location 65 infile.get(ch); // get next char at loc 66 if (ch == '\n') { infile.seekg(-2, std::ios::cur); // move to loc 64 for get() to read loc 65 infile.seekg(-1, std::ios::cur); // move to loc 63 to avoid reading loc 65 infile.get(ch); // get the char at loc 64 ('5') while(ch != '\n') // read each char backward till the next '\n' { infile.seekg(-2, std::ios::cur); infile.get(ch); } string lastLine; std::getline(infile,lastLine); cout << "The last line : " << lastLine << '\n'; } else throw std::exception("check .csv file format"); } std::cin.get(); return 0; } 
0
source

I took the Alexandros decision and adorned it a bit.

 bool moveToStartOfLine(std::ifstream& fs) { fs.seekg(-1, std::ios_base::cur); for(long i = fs.tellg(); i > 0; i--) { if(fs.peek() == '\n') { fs.get(); return true; } fs.seekg(i, std::ios_base::beg); } return false; } std::string getLastLineInFile(std::ifstream& fs) { // Go to the last character before EOF fs.seekg(-1, std::ios_base::end); if (!moveToStartOfLine(fs)) return ""; std::string lastline = ""; getline(fs, lastline); return lastline; } int main() { const std::string filename = "test.txt"; std::ifstream fs; fs.open(filename.c_str(), std::fstream::in); if(!fs.is_open()) { std::cout << "Could not open file" << std::endl; return -1; } std::cout << getLastLineInFile(fs) << std::endl; return 0; } 
0
source

All Articles