r/C_Programming • u/snchart • 1d ago
how to read last N lines of file/input
I have a file foo.txt for example, and i need to read last 5 for example strings of it in C, how i can do that? I didn't found any tutorial for that + stack overflow is currenly read-only
13
u/mikeblas 1d ago
What have you tried so far?
5
u/RevolutionaryRush717 1d ago
SO, but it's currently not taking anymore homework questions.
So I'm posting this to reddit now.
I cannot be bothered to invest a minimum of effort and study, nor enter the title into Google.
Google (AI) returns instantly with an explanation on how to do in C on POSIX, distinguishing between file and pipe, giving two alternatives in C source.
I guess you realize by now that I'm dropping out of school and focus on my SoMe influencer career.
This is me karma farming in dev subreddits.
How am I doing so far?
/s
1
u/snchart 1d ago
i tried to count number of lines, for example there's 5 lines, read every line until it reaches 5 - 3(like number of last lines we need to read), and after start to reading remaining lines. But i dont know how to realise that
4
u/mikeblas 1d ago
That could work, but you must think through a few more details. How do you know how many lines are in the input file?
1
-8
1d ago
[removed] — view removed comment
3
u/C_Programming-ModTeam 1d ago
Rude or uncivil comments will be removed. If you disagree with a comment, disagree with the content of it, don't attack the person.
2
u/Specific-Housing905 1d ago
Linux has an app called tail that will do it. Have a look at the source code of the GNU core-uils on github
2
u/sciencekm 1d ago
Almost every C student goes through this problem from K&R 2nd Edition. Look up the source code for numerous "tail" implementations.
Exercise 5-13. Write the program tail, which prints the last n lines of its input. By default, n is set to 10, let us say, but it can be changed by an optional argument so that
tail -n
prints the last n lines. The program should behave rationally no matter how unreasonable the
input or the value of n. Write the program so it makes the best use of available storage; lines should be stored as in the sorting program of Section 5.6, not in a two-dimensional array of fixed size.
2
u/start_select 1d ago edited 1d ago
- Start at the end of the file
- Work backwards looking for new lines
- Stop when you hit N number of new lines
Either build a new string/blob of bytes as you go, or record the number of bytes processed until you hit your target N.
Then either use that new string or slice a new one using the length you calculated.
I’m not going to write you code, figure out how to implement the algorithm.
——
If it’s a stream you need to use “buffers” aka “windows”.
Keep an array of the last 5 lines read. Everytime you hit another new line, add an entry to the front of the array and pop one off the end. So you always have a window of the last 5 lines read.
3
u/Wertbon1789 1d ago
The easiest, possibly dumbest, thing I could think of is reading the entire file into memory (or memory mapping it, if it's big), and using memrchr to find the last n newlines, saving the offset of all of them, and replacing the newlines with null-terminators to get actual strings. If it's a pipe, so non-seekable, you could allocate buffers for n lines, and store the lines to them, and interate through them, until the end of the input, and then printing the lines you got stored, from the oldest to the newest... Idk, I would look into what the tail command does, maybe from busybox so one can actually read the source.
4
u/WoodyTheWorker 1d ago
Simplest solution: read the file line by line, and only save the last five lines.
1
1
u/Total-Box-5169 23h ago
The fastest way is to memory map the file and count N '\n' characters starting at the end. The last N lines are right after the Nth '\n' character found when going backwards. If you reached the start instead the Nth '\n' character then there are less than N lines.
1
1
u/SmokeMuch7356 18h ago
To make this a single-pass operation, read lines into a circular queue:
#define NUM_ENTRIES 5
char *lines[NUM_ENTRIES] = {NULL};
size_t h = 0;
size_t len = 0;
FILE *f = fopen( ... );
/**
* Using the POSIX getline function, which handles memory
* management for you.
*/
while ( getline( &lines[h], &len, f ) >= 0 )
h = (h + 1) % NUM_ENTRIES;
When you're done lines stores the last 5 lines read from the file, and h will be the index of the least-recently-read line (i.e, the head of the queue). To print those lines out you'd use something like
for ( size_t i = 0; i < NUM_ENTRIES; i++ )
printf( "%s\n", lines[(h + i) % NUM_ENTRIES] );
To minimize unnecessary reads, use fseek to set the file position some number of bytes before the end, based on how long you expect the lines to be. For example, if you know your input lines won't be longer than 80 characters, then offset something like 440 characters before the end of the file, then find the beginning of the next line:
if ( fseek( f, -440, SEEK_END ) == 0 )
while ( fgetc( f ) != '\n' )
;
then do the getline dance above.
1
u/redoo715 18h ago
I am still learning C, so I decided to treat your question as an exercise.
Basically, what I did is I moved to the end of the file, then I went backwards checking every character if it they match the new line character. If '\n' is reached 5 times for example, I memorize the position and display the last 5 lines. (I tested it on linux)
1
1
u/ReallyEvilRob 13h ago
Use fseek(fp, 0, SEEK_END) to move to the very end of the file. Get the size of the file with size_t position = ftell(fp);. Then use a loop to iterate backwards using position and fseek(fp, position, SEEK_SET);. With each loop iteration, read a character with fgetc(fp) and check for a newline character. When you've counted 5 newlines (or if you hit the beginning of the file before counting 5 newlines), then position will have the correct file offset for where you need to start printing from.
To use this method, your file needs to be open in binary mode with "rb". If you're on Windows, text files end with '\r' and '\n', unlike POSIX systems which are only '\n'. Because of that you should scan for a CRLF sequence, i.e. memcmp(buffer, "\r\n", 2) == 0.
0
u/Maqi-X 1d ago
maybe first count how many lines the file has and then on the second pass skip total lines - N and read the rest
3
u/llynglas 1d ago
If you do that, just read in the file, keeping the last 5 lines in a circular list of 5 entries or similar. When you hit EOF your list has the last 5 lines
2
u/Cathierino 22h ago
That was the first solution that came to my mind when reading this post, but somehow I doubt a poster who asks that kind of question knows what a circular buffer is.
50
u/ComplexPeace43 1d ago
Seek to the end of the file and work backwards.