Hi, i am trying to write a program that would work much like the tail command in UNIX. The idea is to print the last 10 lines of a file. I started off by finding out how many lines were in the file, then attempted to seek to the appropriate line number to as to print the last 10 lines, however i then realised seekg was seeking character by character, not by the lines. How can i seek to a specific line?
Here is my code

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

using namespace std;

int main()
{	int lineCount=0;
	fstream file;
	char filename[10];
	string line;

	cout << "Enter file name" << endl;
	cin >> filename;

	file.open(filename);
	if(file.good())
	{	
		cout << "file opened successfully" << endl;

		string t;
		

		while(getline(file, t, '\n'))
		
		++lineCount;
	
		

	}
	else
	{
		cout << "Error file does not exist" << endl;
	}
	file.close();

	file.open(filename);
	if(file.good())
	{	
		int x = lineCount - 10;

		file.seekp (x, ios::beg);
		
		getline (file, line);
		cout << line << endl;
		while(! file.eof() )
   		{
			
			getline (file,line);
      			cout << line << endl;
		}
	}
	file.close();
		
	return 0;
}

Dani AI

Generated

There is no line-aware seek in C++; seekg and tellg work in terms of byte positions in the stream, not line numbers. That means you must map lines to byte offsets yourself and then jump to the offset where the last N lines begin. Ancient Dragon’s offsets idea is on the right track, but use std::streampos (not size_t), capture the start-of-line position before each getline, and seek with seekg (not seekp) because you are reading. If you hit EOF during the first pass, call file.clear() before seeking again. See the references for seekg/tellg behavior. seekg tellg

If you want O(1) memory regardless of file size, you do not need to store every line’s offset. Keep only the last 10 start offsets in a small ring buffer, then seek to the oldest of those and stream to EOF:

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

int main() {
    std::string filename;
    std::getline(std::cin, filename);

    std::ifstream in(filename.c_str()); // open text mode
    if (!in) { std::cerr << "open failed\n"; return 1; }

    std::deque<std::streampos> starts;
    std::string line;

    while (true) {
        std::streampos pos = in.tellg();        // start-of-line
        if (!std::getline(in, line)) break;
        starts.push_back(pos);
        if (starts.size() > 10) starts.pop_front();
    }

    if (starts.empty()) return 0;
    in.clear();                                  // clear eofbit
    in.seekg(starts.front());                    // first of last 10
    while (std::getline(in, line)) std::cout << line << '\n';
}

Notes:

  • ivailosp’s recursive solution is simple but grows the call stack and stores all lines; the deque approach avoids both.
  • Keep the same open mode for reading and seeking; tellg positions are only guaranteed to be valid for the same stream instance. seekg
  • Prefer std::string for the filename (your original char filename[10] can truncate).

Recommended Answers

All 6 Replies

you need a vector<unsigned int> that contain the offsets for each line. Create that array during the loop on line 24. For each line call tellg() to get the offset. Then you can use that info on line 42.

im not to sure if i understand what your saying. I need to create an array of unsigned int? Then store the offset of each line into the array? Then use those values to seek to the desired line??

here is my code, i know it is not what you ask, but i have fun to wright it.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

void read_lines(fstream& file, int& line, string& last_lines){
	string t;
	if (getline(file, t, '\n')){
		read_lines(file, line, last_lines);
	}
	if (line >= 0){
		t+='\n';
		--line;
		last_lines = t + last_lines ;
	}
	else{
		return;
	}
}

int main()
{
	fstream file;
	string filename;

	cout << "Enter file name" << endl;
	getline(cin, filename);

	file.open(filename.c_str());
	if (file.good())
	{
		cout << "file opened successfully" << endl;
		string last_lines = "";
		int lines = 10;
		read_lines(file, lines, last_lines);
		cout << last_lines;
	}
	else
	{
		cout << "Error file does not exist" << endl;
		return 1;
	}
	file.close();

	return 0;
}

that works great! I will try implement a similar method into my own code, thanx for the help :)

im not to sure if i understand what your saying. I need to create an array of unsigned int? Then store the offset of each line into the array? Then use those values to seek to the desired line??

I guess you have never worked with arrays before?? Note: ivailosp had a better idea.

#include<iostream>
#include<fstream>
#include <string>
#include <vector>
using namespace std;

int main()
{
    int lineCount=0;
    fstream file;
    char filename[10];
    string line;
    vector<size_t> offsets; 
    cout << "Enter file name" << endl;
    cin >> filename;

    file.open(filename);
    if(file.good())
    {	
        cout << "file opened successfully" << endl;
        string t;
        while(getline(file, t, '\n'))
        {
            ++lineCount;
            size_t cnt = file.tellg();
            offsets.push_back(cnt);
        }
    }
    else
    {
        cout << "Error file does not exist" << endl;
    }
    file.clear(); // clear eof error flag 
    if(file.good())
    {	
        int x = lineCount - 10;

       file.seekp (offsets[x], ios::beg);
		
        while(getline (file, line))
        {
            cout << line << endl;
        }
    }
    file.close();
		
    return 0;
}

I have used arrays before, but never vectors. Thanx for your help also, both methods seem to work great. I will have to read up on vectors, they look useful.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.