1

How can I traverse a file line by line going from the bottom up? For example, here is my code for traversing it from top to bottom:

void *readFileTB(char *str1)
{
    int size = 1024;
    char *buffer = malloc(size);
    FILE *fp;
    fp = fopen("testfile.txt", "r");
    while(fgets(buffer, 1024, fp)) //get a line from a file
    {
            printf(buffer);
    }
    return 0;
}

If a file contains:

line1onetest
line2twotest
line3threetest

This function will print the following if executed:

line1onetest
line2twotest
line3threetest

How can I write a function that does the above but in the opposite direction so it outputs the following?

line3threetest
line2twotest
line1onetest

Any thoughts?

Lily Banks
  • 69
  • 1
  • 1
  • 6

3 Answers3

5

Line by line it's a little difficult. If we start with bytes, it's pretty simply: we first fseek to a little before the bottom:

if(fseek(fp, 256, SEEK_END) == -1) { /* handle error */ }

Since we've seeked 256 bytes before the end, we can read 256 bytes. Then we can seek back 256 more bytes, etc., until we hit the top of the file.

Now if you're trying to read lines of text, this can be tricky: you need to read a number of bytes at the end of the file and find the last newline character. If there is none, you didn't read enough and you need to read more. Once you've found it, your line starts there. To read the next line, you need to seek backwards again and don't go past your previous line start.

icktoofay
  • 126,289
  • 21
  • 250
  • 231
4

Here, I got a bit into it and coded the entire thing. I have no clue if it's any good, but at least you can get an idea of how it works. (I have a feeling there are better ways to do this)

To compile and use program:

$ gcc -Wall -o reverser main.c

Usage:

$ ./reverser text.txt

text.txt contents:

int main(int argc, char *argv[]){
    if(argc != 2)
        return 1;

    print_rev_file(argv[1], 64);

    return 0;
}

Results:

}
    return 0;

    print_rev_file(argv[1]);

        return 1;
    if(argc != 2)
int main(int argc, char *argv[]){

How to use in code:

main.c

#include <header.h>

int main(int argc, char *argv[]){
    if(argc != 2)
        return 1;

    print_rev_file(argv[1], 64);

    return 0;
}

header.h: Note: It's bad form to use double underscore because many compilers use them.

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

///SUPPORT for get_line

size_t __file_size(FILE ** file); //It'd make sense to homogenize the parameters...
char * __get_line_copy(FILE ** file, size_t chunk_size, size_t position);
char * __get_line(int line_number, FILE ** file, size_t chunk_size);
size_t __nth_line(FILE ** file, int line_number, size_t chunk_size);
unsigned int __line_count(FILE ** file, size_t chunk_size);

#define file_size(x) __file_size(&x)
size_t __file_size(FILE ** file){
    size_t old_pos = ftell(*file);

    fseek(*file, 0, SEEK_END);
    int file_size = ftell(*file);

    fseek(*file, 0, old_pos);
    return file_size;
}

char * __get_line_copy(FILE ** file, size_t chunk_size, size_t position){
    int i;
    char c;
    char * buffer = malloc(chunk_size);
    memset(buffer, 0, chunk_size);

    size_t old_pos = ftell(*file);
    fseek(*file, position, SEEK_SET);

    for(i = 0; (i < chunk_size && (c = fgetc(*file)) != '\n' && !feof(*file)); i++){
        *(buffer+i) = c;
    }

    *(buffer+chunk_size) = '\0';

    fseek(*file, 0, old_pos);
    return buffer;
}

#define FIRST 0
#define LAST -1
#define get_line(x, y, z) __get_line(x, &y, z);

char * __get_line(int line_number, FILE ** file, size_t chunk_size){
    char * line = __get_line_copy(file, chunk_size, __nth_line(file, line_number, chunk_size));

    return line;
}

size_t __nth_line(FILE ** file, int line_number, size_t chunk_size){
    int i = 0, index;
    size_t old_pos = ftell(*file);
    fseek(*file, 0, SEEK_SET);

    if(line_number > 0){
        while(i <= line_number && !feof(*file)){
            if(fgetc(*file) == '\n')
                i++;
        }
    } else {
        while(!feof(*file)){
            if(fgetc(*file) == '\n')
                i++;
        }

        index = i + (line_number+1);
        fseek(*file, 0, SEEK_SET);
        int i = 0;

        while(i < index){
            if(fgetc(*file) == '\n')
                i++;
        }
    }

    size_t position = ftell(*file);

    fseek(*file, 0, old_pos);
    return position;
}

#define line_count(x, y) __line_count(&x, y)
unsigned int __line_count(FILE ** file, size_t chunk_size){
    int i = 1;

    while(!feof(*file))
        if(fgetc(*file) == '\n')
            i++;

    return i;
}

int print_rev_file(char * filename, size_t buffer){
    FILE * file = fopen(filename, "r");
    if(file == NULL){
        return -1;
    }

    int i, lines = line_count(file, buffer);
    for(i = 0; i < lines; i++){
        char * line = get_line(LAST-i, file, buffer);
        puts(line);
        free(line);
    }


    return 0;
}

int main(int argc, char *argv[]){
    if(argc != 2)
        return 1;

    print_rev_file(argv[1], 64);

    return 0;
} 
GRAYgoose124
  • 621
  • 5
  • 24
1

There's a utility in the GNU coreutils called tac that does exactly that.

You can view the source for it below.

http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob_plain;f=src/tac.c;hb=HEAD

Shawn Balestracci
  • 7,380
  • 1
  • 34
  • 52