0

I am trying to think of logic on how to read the last line of a file but I cannot come up with answers. ACCOUNTNUM info is a structure. my .dat file who has 3 acc numbers already. Here I want to know how to get the last account number/line which is 2022-3.

This is the function

LastAccountNumber(){
    ACCOUNTNUM info;
    file = fopen("acctNumbers.dat","r");
    char firstAcc[50]="2022-1";//ignore this I was gonna compare this with the last line.
    
    while((fread(&info,sizeof(struct accountNum),1,file))==1){
            printf("\nAcc No %s",info.sAccountNum);
    }
        
    }
    
}

This is my structure

struct accountNum{
    int sequence;
    char sAccountNum[16];
};
typedef struct accountNum ACCOUNTNUM;

Content of my acctNumbers.dat file.

2022-1       ú·   2022-2       ú·   2022-3       ú·
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
clownzxy
  • 19
  • 8
  • 3
    Your code doesn't read lines but records. Lines have a (potential) different size, records haven't. To read the last record you can get the file size, subtract the record size, and `fseek`() to `filesize-recordsize`. – harper May 02 '22 at 10:59
  • Is your dat file a binary file? How is it created and data stored in it? – Aval Sarri May 02 '22 at 10:59
  • To read last *line*:Read last X chars (16 for testing it works, more for final version). Did you find end of *2nd last line*? If not, keep reading in chunks of X chars. Once you find end of 2nd last line, seek after it, allocate right size of buffer, read last line. Note that this can be optimized (like, do not read anything twice, set read buffer size to block size on disk, align reads at multiple of X from start of file instead of end...), but first get the crude version working. – hyde May 02 '22 at 11:01
  • @harper Oh i see, so its like this. I get the file size of acctNumber.dat then subtract the record sizes which is (3) because I have 3 data and use that in fseek() function to get the last record? – clownzxy May 02 '22 at 11:03
  • @AvalSarri i created the .dat file with notepad. I stored data with fwrite() – clownzxy May 02 '22 at 11:04
  • Please post the content of the input file as text, not as an image. You may want to read this for further information: [Why not upload images of code/data/errors when asking a question?](https://meta.stackoverflow.com/q/285551/12149471) – Andreas Wenzel May 02 '22 at 11:06
  • No need to create file with notepad, fwrite will create it for you. – anastaciu May 02 '22 at 11:16
  • 1
    It would probably be better to also provide the input as a [hex dump](https://en.wikipedia.org/wiki/Hex_dump). On Linux, you can use the [`hexdump`](https://man7.org/linux/man-pages/man1/hexdump.1.html) command-line utility to create one. I recommend `hexdump -C`. You can also use a hex editor. See [this question](https://stackoverflow.com/questions/1724586/can-i-hex-edit-a-file-in-visual-studio) on how to do create a hex dump in Visual Studio and [this question](https://stackoverflow.com/questions/38905181/how-do-i-see-a-bin-file-in-a-hex-editor-in-visual-studio-code) for Visual Studio Code. – Andreas Wenzel May 02 '22 at 11:19
  • @clownzxy The record size is not 3. It's 16+sizeof(int), that is 20 for a 32-bit system. On 64-bit system the record size is 24. To get "the last record" shouldn't need the information about the actual count of records. Therefore you need the file size. Seeking to the last record has the benefit that it works with the same performance independent of the file size. Reading billions of records just to get to the end can be tedious. – harper May 02 '22 at 13:00
  • @AndreasWenzel The file structure is already well documented with `struct accountNum`. The actual size of the integer could be detected by the hex dump. But this doesn't change any recommendation for the implementation. – harper May 02 '22 at 13:03
  • Aside: Consider `while((fread(&info,sizeof(struct accountNum),1,file))==1){` --> `while(fread(&info, sizeof info, 1, file) == 1){`. Easier to review and maintain. – chux - Reinstate Monica May 02 '22 at 15:14

2 Answers2

2

You could call fread in a loop until it returns 0, and then print the last record read:

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

typedef struct accountNum
{
    int sequence;
    char sAccountNum[16];

} ACCOUNTNUM;

int main( void )
{
    FILE *fp;
    ACCOUNTNUM info;
    bool success = false;

    //open file and verify that it is open
    fp = fopen( "acctNumbers.dat", "r" );
    if ( fp == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    //skip to last record
    while( ( fread( &info, sizeof info, 1, fp) ) == 1 )
    {
        //set variable to indicate that we have found at
        //least one record
        success = true;
    }

    //print last record, if it exists
    if ( success )
        printf( "Acc No: %s\n", info.sAccountNum );
    else
        printf( "No records found.\n" );

    //cleanup
    fclose( fp );
}

However, this is only guaranteed to work if you are sure that the last fread will not perform a partial read, i.e. if you are sure that the file size is an exact multiple of sizeof(ACCOUNTNUM). Because if fread does perform a partial read, then the buffer content will be indeterminate.

If you cannot exclude the possibility of a partial read, then you could use two buffers for reading, and use them alternately:

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

typedef struct accountNum
{
    int sequence;
    char sAccountNum[16];

} ACCOUNTNUM;

int main( void )
{
    FILE *fp;
    ACCOUNTNUM info[2];
    int current_index = 0;
    bool success = false;
    size_t ret;

    //open file and verify that it is open
    fp = fopen( "acctNumbers.dat", "r" );
    if ( fp == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    for (;;) //infinite loop, equivalent to while(1)
    {
        //read record from file
        ret = fread( &info[current_index], sizeof *info, 1, fp);

        //swap buffer index
        current_index = current_index == 0 ? 1 : 0;

        //restart loop if successful
        if ( ret == 1 )
        {
            //set variable to indicate that we have found at
            //least one record
            success = true;

            continue;
        }

        //break out of loop
        break;
    }

    //print last record, if it exists
    if ( success )
        printf( "Acc No: %s\n", info[current_index].sAccountNum );
    else
        printf( "No records found.\n" );

    //cleanup
    fclose( fp );
}

Or you could use a single buffer and change the way you are calling the function fread, by swapping the second and third function arguments, so that you can detect whether a partial read occurred. If it does occur, you can print an error message and terminate the program.

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

typedef struct accountNum
{
    int sequence;
    char sAccountNum[16];

} ACCOUNTNUM;

int main( void )
{
    FILE *fp;
    ACCOUNTNUM info;
    bool success = false;
    size_t ret;

    //open file and verify that it is open
    fp = fopen( "acctNumbers.dat", "r" );
    if ( fp == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    //read one record per loop iteration
    while ( ( ret = fread( &info, 1, sizeof info, fp) ) != 0 )
    {
        //verify that no partial read occurred
        if ( ret != sizeof info )
        {
            fprintf( stderr, "Error: Partial read detected!\n" );
            exit( EXIT_FAILURE );
        }

        //set variable to indicate that we have found at
        //least one record
        success = true;
    }

    //print last record, if it exists
    if ( success )
        printf( "Acc No: %s\n", info.sAccountNum );
    else
        printf( "No records found.\n" );

    //cleanup
    fclose( fp );
}
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
1

You are reading records one by one and storing it in info, it stands to reason that the element ACCOUNTNUM stored in info when the cycle ends is exactly the last record in the file, take the following code which is a slight modification of yours, to illustrate the point:

#include <stdio.h>

struct accountNum {
    int sequence;
    char sAccountNum[16];
};
typedef struct accountNum ACCOUNTNUM;
int LastAccountNumber(ACCOUNTNUM* info) {

    int success = 0;
    FILE* file = fopen("acctNumbers.dat", "rb");
    if (file == NULL) {
        return -1;  // Allows you to document different types of errors
    }
    while ((fread(info, sizeof(struct accountNum), 1, file)) == 1) {
        success = 1;
    }
    fclose(file);
    return success;
}

int main() {
    ACCOUNTNUM N[] = {{123, "2022-1"}, {111, "2022-2"}, {321, "2022-3"}};
    FILE* file = fopen("acctNumbers.dat", "wb");

    fwrite(N, 1, sizeof N, file);
    fclose(file);

    ACCOUNTNUM info;

    if (LastAccountNumber(&info) == 1) //if -1 couldn't open file, 0 no record read
        printf("\nAcc No %s", info.sAccountNum);
}

Will output:

Acc No 2022-3

Which is exactly the last element in the file.

Live sample: https://onlinegdb.com/q760Ow1vQc

Note that you should verify the return value of fopen to confirm the correct access to the file. Closing the file after you are done with it is also advised.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • 1
    One problem with using `fread` is that the buffer content will be indeterminate after a partial read. That is why I use two alternating buffers in the second code snippet of my answer. However, with binary files, you can probably generally rely on there not being any additional bytes, so I am still upvoting this answer. – Andreas Wenzel May 02 '22 at 12:22
  • @AndreasWenzel I agree with your comment, and it would be a shame if your very thorough answer would have no votes ;) We're probably missing an answer with the option of finding the file size and reading the last entry using the size of one element as offset. – anastaciu May 02 '22 at 12:39
  • 2
    Unfortunately, ISO C does not provide a reliable way to determine the file size without reading the entire file. [§7.21.9.2 ¶3 of the ISO C11 standard](http://port70.net/~nsz/c/c11/n1570.html#7.21.9.2) states the following: "A binary stream need not meaningfully support fseek calls with a whence value of SEEK_END." – Andreas Wenzel May 02 '22 at 12:45