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       ú·
11
  • 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. Commented May 2, 2022 at 10:59
  • Is your dat file a binary file? How is it created and data stored in it? Commented May 2, 2022 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. Commented May 2, 2022 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? Commented May 2, 2022 at 11:03
  • 1
    It would probably be better to also provide the input as a hex dump. On Linux, you can use the hexdump command-line utility to create one. I recommend hexdump -C. You can also use a hex editor. See this question on how to do create a hex dump in Visual Studio and this question for Visual Studio Code. Commented May 2, 2022 at 11:19

2 Answers 2

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 );
}
Sign up to request clarification or add additional context in comments.

Comments

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.

3 Comments

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.
@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.
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 states the following: "A binary stream need not meaningfully support fseek calls with a whence value of SEEK_END."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.