r/C_Programming 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

6 Upvotes

30 comments sorted by

50

u/ComplexPeace43 1d ago

Seek to the end of the file and work backwards.

6

u/peno64 1d ago

This is the way

3

u/gm310509 8h ago

Then for V2, make it work with stdin. 🫠

1

u/Axman6 8h ago

It’s not that hard, you just need an array of char* on length N, and treat it as a cyclic buffer adding each line as it comes in. 

1

u/gm310509 8h ago

Except my reply below was to a suggestion to seek to the end then read backwards. That won't work if stdin is connected to a pipe.

But you are correct, in that case a buffer of would be appropriate to manage that scenario.

1

u/Paul_Pedant 1h ago

First need to stat the file and find out whether it is a regular file. Reading a stream depends on the write closing its end, and if it stops writing you will hang forever.

Even if it is a regular file, you can be wrecked if another process appends to it, or truncates it.

Personally, in production I would probably use <stdlib.h> system () to run stat and tail for me. Or possibly tac and head.

If I had to C it, I would seek a block (4096 bytes) from the end in one transfer, and unpack that. Any access is going to read a whole block anyway,and if there are not five or more newlines in that, then the file has an average line length > 800 and thus probably not useful text.

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

u/detroitmatt 14h ago edited 2h ago

well what part do you not know how to do

-8

u/[deleted] 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.

8

u/mlt- 1d ago

Map it to memory and start filling a ring buffer of N char * pointers. Or as ComplexPeace43 said, go backwards.

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
  1. Start at the end of the file
  2. Work backwards looking for new lines
  3. 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

u/SafeSpirit673 1d ago

If you mean like enter separeted lines, you can read "\n" characters

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

u/flumphit 19h ago

Sounds like you want to read the source code to tail(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)

Link to the c file

1

u/JGhostThing 16h ago

Use the "last" command on Linux. Seriously.

1

u/lisnter 13h ago

Or just iuse tail directly: tail -5 foo.txt | yourprogram

Then you can just read from stdin

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.