8

file looks like this:

abcd
efgh
ijkl

I want to read the file using C so that it read the last line first:

ijkl
efgh
abcd

I cannot seem to find a solution that does not use an array for storage. Please help.

edit0: Thanks for all the answers. Just to let you know, I am the one creating this file. So, can I create in a way its in the reverse order? Is that possible?

hari
  • 9,439
  • 27
  • 76
  • 110
  • See related: http://stackoverflow.com/questions/136168/get-last-n-lines-of-a-file-with-python-similar-to-tail –  Aug 03 '11 at 07:23
  • 3
    There already exists a command line utility that is called `tac` (the reverse of cat). You could fetch the source code for that and study how this program solves the problem. – hlovdal Aug 03 '11 at 07:56
  • 1
    Is there any reason that you're knee-capping yourself by not being able to use an array? – mu is too short Aug 03 '11 at 08:04
  • @hlovdal: Thanks, looking into `tac` source. – hari Aug 03 '11 at 16:40
  • @mu is too short: because the number of lines can be **Huge** – hari Aug 03 '11 at 16:40

6 Answers6

13

It goes like this:

  1. Seek to one byte before the end of the file using fseek. There's no guarantee that the last line will have an EOL so the last byte doesn't really matter.
  2. Read one byte using fgetc.
  3. If that byte is an EOL then the last line is a single empty line and you have it.
  4. Use fseek again to go backwards two bytes and check that byte with fgetc.
  5. Repeat the above until you find an EOL. When you have an EOL, the file pointer will be at the beginning of the next (from the end) line.
  6. ...
  7. Profit.

Basically you have to keep doing (4) and (5) while keeping track of where you were when you found the beginning of a line so that you can seek back there before starting your scan for the beginning of the next line.

As long as you open your file in text mode you shouldn't have have to worry about multibyte EOLs on Windows (thanks for the reminder Mr. Lutz).

If you happen to be given a non-seekable input (such as a pipe), then you're out of luck unless you want to dump your input to a temporary file first.

So you can do it but it is rather ugly.

You could do pretty much the same thing using mmap and a pointer if you have mmap available and the "file" you're working with is mappable. The technique would be pretty much the same: start at the end and go backwards to find the end of the previous line.


Re: "I am the one creating this file. So, can I create in a way its in the reverse order? Is that possible?"

You'll run into the same sorts of problems but they'll be worse. Files in C are inherently sequential lists of bytes that start at the beginning and go to the end; you're trying to work against this fundamental property and going against the fundamentals is never fun.

Do you really need your data in a plain text file? Maybe you need text/plain as the final output but all the way through? You could store the data in an indexed binary file (possibly even an SQLite database) and then you'd only have to worry about keeping (or windowing) the index in memory and that's unlikely to be a problem (and if it is, use a "real" database); then, when you have all your lines, just reverse the index and away you go.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • 2
    Your solution is not efficient as the `fseek` is a slow operation and you are doing it for every byte in the file. – Skizz Aug 03 '11 at 07:44
  • @Skizz: I never said it was efficient but I did say it was ugly. Do you have anything better that doesn't use an array? There's always `mmap` I suppose. – mu is too short Aug 03 '11 at 07:47
  • 2
    The standard `f*` IO functions, when the `FILE *` is opened in text (not binary) mode, automatically do the EOL conversions for you. – Chris Lutz Aug 03 '11 at 07:59
  • @Chris: Right, thanks. Been a long time since I've had to deal with C on Windows. – mu is too short Aug 03 '11 at 08:02
3

In pseudocode:

open input file
while (fgets () != NULL)
{
   push line to stack
}
open output file
while (stack no empty)
{
   pop stack
   write popped line to file
}

The above is efficient, there is no seek (a slow operation) and the file is read sequentially. There are, however, two pitfalls to the above.

The first is the fgets call. The buffer supplied to fgets may not be big enough to hold a whole line from the input in which case you can do one of the following: read again and concatenate; push a partial line and add logic to the second half to fix up partial lines or wrap the line into a linked list and only push the linked list when a newline/eof is encountered.

The second pitfall will happen when the file is bigger than the available ram to hold the stack, in which case you'll need to write the stack structure to a temporary file whenever it reaches some threshold memory usage.

Skizz
  • 69,698
  • 10
  • 71
  • 108
  • 1
    This answer is bad in many ways: it uses a lot of memory while there is no need for it. Seek is not slow. Perhaps in Windows, or network file systems, but not on Unix. – Coroos Mar 13 '19 at 10:17
  • @Coroos: True, the seek function itself isn't slow, just using it to read a file backwards is, i.e. where do you seek to? Read the file one character at a time from the end to the begining? That can be done, and done well, but the code is starting to get a bit complicated. Here, "efficient" is also a measure of the code complexity, this code is trivial. – Skizz Mar 20 '19 at 19:37
  • Reading blocks of data from the end forwards and then scanning the blocks backwards is more complex. The storage medium will probably affect the speed of the algorithm more than the code, reading a file forwards is never slower (not always faster, but never slower). The reading the file backwards approach will be superior when there is limited ram available though. Swings and roundabouts. – Skizz Mar 20 '19 at 19:37
2

The following code should do the necessary inversion:

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

int main(int argc, char *argv[])
{
        FILE *fd;
        char len[400];
        int i;

        char *filename = argv[1];
        int ch;
        int count;

        fd = fopen(filename, "r");
        fseek(fd, 0, SEEK_END);
        while (ftell(fd) > 1 ){
                fseek(fd, -2, SEEK_CUR);
                if(ftell(fd) <= 2)
                        break;
                ch =fgetc(fd);
                count = 0;
                while(ch != '\n'){
                        len[count++] = ch;
                        if(ftell(fd) < 2)
                                break;
                        fseek(fd, -2, SEEK_CUR);
                        ch =fgetc(fd);
                }
                for (i =count -1 ; i >= 0 && count > 0  ; i--)
                        printf("%c", len[i]);
                printf("\n");
        }
        fclose(fd);
}
Shehbaz Jaffer
  • 1,944
  • 8
  • 23
  • 30
-1

The following works for me on Linux, where the text file line separator is "\n".

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

void readfileinreverse(FILE *fp)
{
    int i, size, start, loop, counter;
    char *buffer;
    char line[256];
    start = 0;
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);

    buffer = malloc((size+1) * sizeof(char));

    for (i=0; i< size; i++)
    {
        fseek(fp, size-1-i, SEEK_SET);
        buffer[i] = fgetc(fp);

        if(buffer[i] == 10)
        {
           if(i != 0)
           {
            counter = 0;        
            for(loop = i; loop > start; loop--)
            {
                if((counter == 0) && (buffer[loop] == 10))
                {
                    continue;
                }               
                line[counter] = buffer[loop];
                counter++;
            }
            line[counter] = 0;
            start = i;
            printf("%s\n",line);
           }
        }
    }

    if(i > start)
    {    
        counter = 0;
        for(loop = i; loop > start; loop--)
        {       
            if((counter == 0) && ((buffer[loop] == 10) || (buffer[loop] == 0)))
            {
                continue;
            }               
            line[counter] = buffer[loop];
            counter++;
        }
        line[counter] = 0;
        printf("%s\n",line);

        return;
    }
}

int main()
{
    FILE *fp = fopen("./1.txt","r");
    readfileinreverse(fp);
    return 0;
}
Bertrand
  • 7
  • 1
  • It seems to me that you have a memory leak. You use `malloc()` to allocate memory for `buffer`, but in the end of the function `readfileinreverse()` you don't call `free(buffer)`. – iamantony Mar 25 '16 at 09:19
-2

Maybe , The does the trick , It reverse content of the file in whole just like a string

  1. Define a variable of type string with size of your file
  2. Get Contents of the file and store in the variable
  3. Use strrev() to reverse the string.

You can later on display the output or even write it to a file. The code goes like this:

#include <stdio.h>
#include <String.h>

int main(){
    FILE *file;
    char all[1000];

    // give any name to read in reverse order
    file = fopen("anyFile.txt","r");

    // gets all the content and stores in variable all
    fscanf(file,"%[]",all);

    // Content of the file 
    printf("Content Of the file %s",all);

    // reverse the string 
    printf("%s",strrev(all));
    fclose(file);
    return 0;
}
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
Niamatullah Bakhshi
  • 1,445
  • 16
  • 27
-2

I know this question has been awnsered, but the accepted awnser does not contain a code snippet and the other snippets feel too complex. This is my implementation:

#include <stdio.h>

long file_size(FILE* f) {
    fseek(f, 0, SEEK_END); // seek to end of file
    long size = ftell(f); // get current file pointer
    fseek(f, 0, SEEK_SET); // seek back to beginning of file
    return size;
}

int main(int argc, char* argv[]) {
    FILE *in_file = fopen(argv[1], "r");
    long in_file_size = file_size(in_file);
    printf("Got file size: %ld\n", in_file_size);

    // Start from end of file
    fseek(in_file, -1, SEEK_END); // seek to end of file
    for (int i = in_file_size; i > 0; i--) {
        char current_char = fgetc(in_file); // This progresses the seek location
        printf("Got char: |%c| with hex: |%x|\n", current_char, current_char);
        fseek(in_file, -2, SEEK_CUR); // Go back 2 bytes (1 to compensate)
    }
    printf("Done\n");

    fclose(in_file);
}
bart
  • 155
  • 7