Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C programming, getting the last line of file

I am writing a c program that opens a txt file and want to read the last line of the txt file. I am not that proficient in C so bear in mind that I may not know all of the concepts in C. I am stuck at the part where I use fscanf to read all the lines of my txt file but I want to take the last line of the txt file and get the values as described below.

Here is my incomplete code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

FILE *sync;


void check()
{
    int success; //to hold the results if the timestamps match
    sync = fopen("database.txt","r");
    char file[] = "database.txt";

    while (fscanf(sync, "%d.%06d", &file) != EOF)
    {


    }

    fclose(sync);
}

sample txt file:

/////// / //// ///// ///// //////////////// Time: 1385144574.787665 //////// /
/////// / //// ///// ///// //////////////// Time: 1385144574.787727 //////// /
/////// / //// ///// ///// //////////////// Time: 1385144574.787738 //////// /
/////// / //// ///// ///// //////////////// Time: 1385144574.787746 //////// /
/////// / //// ///// ///// //////////////// Time: 1385144574.787753 //////// /

The / are some words, symbols and numbers I do not want, just the numbers in sample txt as shown above

I appreciate any examples and pointing out errors I made so I can understand this much better. Since I made some people confused about the text file, here is what it really is. This is the format it will be so I should know the length of each line. However, I will not be able to know how many lines there will be as it may be updated.

 Socket: 0 PGN: 65308 Data: 381f008300000000 Time: 1385144574.787925 Address: 28
 Socket: 0 PGN: 65398 Data: 0000000100000000 Time: 1385144574.787932 Address: 118
 Socket: 0 PGN: 61444 Data: f07d83351f00ffff Time: 1385144574.787940 Address: 4
 Socket: 0 PGN: 65266 Data: 260000000000ffff Time: 1385144574.787947 Address: 242
 Socket: 0 PGN: 65309 Data: 2600494678fff33c Time: 1385144574.787956 Address: 29
 Socket: 0 PGN: 65398 Data: 0000000100000000 Time: 1385144574.787963 Address: 118
 Socket: 0 PGN: 61444 Data: f07d833d1f00ffff Time: 1385144574.787971 Address: 4
 Socket: 0 PGN: 65398 Data: 0000000100000000 Time: 1385144574.787978 Address: 118
 Socket: 0 PGN: 61443 Data: d1000600ffffffff Time: 1385144574.787985 Address: 3
 Socket: 0 PGN: 65308 Data: 451f008300000000 Time: 1385144574.787993 Address: 28
 Socket: 0 PGN: 65317 Data: e703000000000000 Time: 1385144574.788001 Address: 37

Again I am after the Time values (eg. 1385144574.787925) at the last line of the txt file. Hope this helps.

like image 570
GhostMember Avatar asked Oct 25 '25 09:10

GhostMember


2 Answers

Since you're after the last line of the file, and you didn't mention how large the file might be, it could be worth while to start reading the file from the end, and work your way backwards from there:

FILE *fp = fopen("database.txt", "r");
fseek(fp, 0, SEEK_END);//sets fp to the very end of your file

From there, you can use fseek(fp, -x, SEEK_CUR); where x is the number of bytes you want to go back, until you get to where you want... other than that, Jekyll's answer should work just fine.
However, to get the last line, I tend to do something like this:

FILE *fp = fopen("database.txt", "r");
char line[1024] = "";
char c;
int len = 0;
if (fp == NULL) exit (EXIT_FAILURE);
fseek(fp, -1, SEEK_END);//next to last char, last is EOF
c = fgetc(fp);
while(c == '\n')//define macro EOL
{
    fseek(fp, -2, SEEK_CUR);
    c = fgetc(fp);
}
while(c != '\n')
{
    fseek(fp, -2, SEEK_CUR);
    ++len;
    c = fgetc(fp);
}
fseek(fp, 1, SEEK_CUR);
if (fgets(line, len, fp) != NULL) puts(line);
else printf("Error\n");
fclose(fp);

The reasoning behind my len var is so that I can allocate enough memory to accomodate the entire line. Using an array of 1024 chars should suffice, but if you want to play it safe:

char *line = NULL;
//read line
line = calloc(len+1, sizeof(char));
if (line == NULL)
{
    fclose(fp);
    exit( EXIT_FAILURE);
}
//add:
free(line);//this line!
fclose(fp);

Once you've gotten that line, you can use Jekyll's sscanf examples to determine the best way to extract whatever you want from that line.

like image 189
Elias Van Ootegem Avatar answered Oct 26 '25 22:10

Elias Van Ootegem


The way you are using fscanf is wrong as the actual vector of arguments needs to match what you are collecting (as you can see in the manpage). Instead of using fscanf you may consider using fgets and then filtering for what you are looking for in the latest raw with a regex through sscanf.

Note:: I collected the value in double format, you may choose the format that suits you the most for your problem (string?int.int?float?), in order to do this you should check for regex using scanf. Please come back if you cannot accomplish this task.

update:: due to some requests I wrote some few examples of different pattern matching. These should be a good starting point to fix your problems.

update:: 1. I have seen that you added the pattern of your db file so we can now state that both #3 and #4 match and put the 3 here (faster). 2. I removed the feof check as for your request, but note that the check is fine if you know what you are doing. Basically you have to keep in mind that stream's internal position indicator may point to the end-of-file for the next operation, but still, the end-of-file indicator may not be set until an operation attempts to read at that point. 3. You asked to remove the char line[1024]={0,}; This instruction is used to initialize the line[1024] array which will contain the lines that you read from the file. This is needed! To know what that instruction is please see here

Code:

void check()
{
   char line[1024]={0,}; // Initialize memory! You have to do this (as for your question)
   int n2=0;
   int n3=0;
   sync = fopen("database.txt", "r");
   if( sync ) {
      while( fgets(line, 1024, sync) !=NULL ) {
      // Just search for the latest line, do nothing in the loop
      } 
      printf("Last line %s\n", line); //<this is just a log... you can remove it
      fclose(sync);
      // This will look for Time and it will discard it collecting the number you are looking for in n2 and n3
      if (sscanf(line, "%*[^T]Time: %d.%d", &n2, &n3) ) {
          printf( "%d.%d\n", n2, n3);
      }
   }
}

Example 2
if for instance you need to collect the value using two integers you will need to replace the sscanf of the example above with the following code:

  unsigned int n2, n3;
  if (sscanf(line, "%*[^0-9]%d.%d", &n2, &n3) ) {
    printf( "%d.%d\n", n2, n3);
  }

said this you should figure out how to collect other formats.

Example 3 A better regex. In case there are others number in the file before the giving pattern you may want to match on Time, so let's say that there isn't any T before. A regex for this can be:

 if (sscanf(line, "%*[^T]Time: %d.%d", &n2, &n3) ) {
    printf( "%d.%d\n", n2, n3);
}

The regex using sscanf can be not suitable for your pattern, in that case you need to consider the usage of gnu regex library or you can mix strstr and sscanf like I did in the following example.

Example 4 This can be useful if you don't find a common pattern. In that case you may want to trigger on the string "Time" using strstr before calling the sscanf

  char *ptr = strstr( line, "Time:" );
  if( ptr != NULL ) {
     if (sscanf(ptr, "%*[^0-9]%d.%d", &n2, &n3) ) {
        printf( "%d.%d\n", n2, n3);
     }
  }

* Note * You may need to find your way to parse the file and those above can be only suggestions because you may have more specific or different patterns in your file but the instruction I posted here should be enough to give you the instruments to do the job in that case

like image 32
18 revsJekyll Avatar answered Oct 26 '25 22:10

18 revsJekyll