2

I'm trying to copy the file itself with the same permissions, same size, same all to another one (new), I did it but copying the content normal, but now trying to copy it backwards (last line is the first on the new file), how I planned this is, first get the number of lines of a file as follows :

while (!feof(fp)) {
    ch = fgetc(fp);
    if (ch == '\n') {
        lines++;
    }
}

Then create an array to put each line from the file, once I have that array full with a for

for (unsigned i = n; i-- > 0;) {
    //n is the number of lines
}

Then on this for I write to a doc then it will be written backwards, is there a better option to do this?

Could you give me an example if so?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
StuartDTO
  • 783
  • 7
  • 26
  • 72

4 Answers4

3

I shaln't write full code here as you seem to mostly be having issues with design.

But you should be able to do do what you want by:

  1. Reading the entire file into a string with a big read or fread
  2. Using strchror alternatively strtok (simpler but with caveats) to find all the newlines, storing their positions into an array of pointers (realloc is your friend if you don't know how many lines there are up front).
  3. Finally write each string to the file going backwards through your array of pointers with newlines in between (With write or fwrite) (You will need to remember the number of lines in a variable somewhere).

Please note, I have mentioned two possible library functions to use for step 2. strtok is probably the easiest function to use however has a few caveats such as ignoring empty lines, not being thread-safe and modifying an input argument so I would only use it for quick hacking around.

Therefore if you want the program to work as well as possible strchr would be more appropriate. However this will be slightly harder to use. Most of all you need to remember each time calling it successively to call it on the char after the result of the last call to it, and to make sure to do null checks for the end of the string. Also you will need to ensure you only write up to the next newline when writing the lines back out as strchr will not replace them with null characters.

Vality
  • 6,577
  • 3
  • 27
  • 48
  • 's method is simpler than mine. Seek to the end of the file to find its length, then allocate the file-size buffer to read. – stark Jan 23 '17 at 22:50
  • 3
    @Stuart Notice how this answer has broken the problem into discrete steps. Most of the description is in English while keeping in mind a few tools that are available from the C standard libraries. This is always a good way to tackle solving any coding problem. – Code-Apprentice Jan 23 '17 at 22:50
  • @stark Regarding the use of `realloc`, I meant for the array of pointers to the starts of lines, we cant know how many of those there are in advance, I wasn't specifically advocating any specific method for calculating file length. But if you feel it would make the answer better I will happily edit that in, or you can if you like. :-) – Vality Jan 23 '17 at 22:53
  • Note: `strtok()` is terrible: it will treat "\n\n" as one separator. – wildplasser Jan 23 '17 at 23:07
  • @wildplasser Yeah, I agree and would never use strtok in production code (if nothing else it's not thread safe and modifies input arguments) however will live with it in a toy program which this is. However feel free to add a warning about it to the answer in case the OP plans to use it for more than this. – Vality Jan 23 '17 at 23:09
  • IMHO, even in toy problems like this, `strtok()` is a bad choice. – wildplasser Jan 23 '17 at 23:12
  • @wildplasser I dont know if you are the downvoter, and I dont mind either way, but I added some more info about `strtok` and an alternative using `strchr`. Please let me know if you think that a better approach. Or if you have any other ideas. – Vality Jan 24 '17 at 01:32
  • Yes I was. But: *you* give the good? advice, and IMO *you* should explain why `strtok()` is such an excellent function, instead of mentioning it has some "caveats". – wildplasser Jan 24 '17 at 08:46
1

For moderately large files with moderately long lines, here is a quick and dirty recursive implementation:

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

int main(void) {
    static char buf[10000];
    if (fgets(buf, sizeof(buf), stdin)) {
        char *str = strdup(buf);
        main();
        fputs(str, stdout);
        free(str);
    }
    return 0;
}

This is a very simple filter: it reads the file from standard input and writes the lines in reverse order to standard output.

Now since you did not do any coding, your task will be to identify the potential problems with this approach.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
1
  • read the file into memory (if it fits...)
  • reverse the whole file (character-wise)
  • reverse the characters in the resulting lines
  • write the file
  • please dont reformat. I have a right to have a different indenting style, and I don't like yours either.

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

int main(int argc, char **argv)
{
size_t size, beg, end, this, used;
char*all = NULL;
int ch;
FILE *fp;

fp = fopen (argv[1], "rb+" );
if (!fp) {
        perror("open failed");
        exit(EXIT_FAILURE);
        }

fprintf(stderr,"Reading file...\n");
for (size = used=0;1; used++) {
        ch = getc(fp);
        if (used+1 >= size) {
                size_t newsize;
                newsize = (size==0) ? 1024: size *2;
                fprintf(stderr,"Realloc %zu <<--%zu ...\n", size, newsize);
                all = realloc(all, newsize);
                if (!all) exit(EXIT_FAILURE);
                size = newsize;
                }
        if (ch == EOF) break;
        all[used] = ch;
        }
all[used] = 0;

fprintf(stderr,"Read %zu / %zu ...\n", used, size );
rewind(fp);

fprintf(stderr, "Inverting file...\n");
for (beg = 0, end = used; beg < --end ; beg++) {
        ch = all[beg];
        all[beg] = all[end];
        all[end] = ch;
        }

fprintf(stderr,"beg/End= %zu / %zu ...\n", beg, end );
rewind(fp);
fprintf(stderr,"Inverting lines...\n");
for (beg = 0; beg < used; beg = this) {
        char *nl = strchr(all+beg+1, '\n' );
        if (nl) end = nl - all;
        else end = used;
        for(this = end; beg < --end; beg++) {
                ch = all[beg];
                all[beg] = all[end];
                all[end] = ch;
                }
        }

 /* at this point, you could fclose(fp); the input file,
 ** and open a new file for the result, using
 ** fp = fopen("somename", "w");
 ** But here, the result is written back into the original file.
 */

rewind(fp);

fprintf(stderr,"Writing file...\n");
for (beg =0;beg < used; beg++) {
        putc(all[beg] , fp);
        }

fprintf(stderr,"Closing file...\n");
fclose(fp);
return 0;
}
wildplasser
  • 43,142
  • 8
  • 66
  • 109
0

Here's a way to do the line reverse using mmap to access the files. It should be faster for larger files.

I've tested this and it appears to handle most edge cases.

To improve the speed even more, you could add madvise calls.

Side note: Sorry about the _GNU_SOURCE. It's to get the definition of memrchr. If your system doesn't have that, it's easy enough to create one.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

#define sysfault(_fmt...) \
    do { \
        fprintf(stderr,_fmt); \
        exit(1); \
    } while (0)

#ifdef DEBUG
#define dbgprt(_fmt...)     fprintf(stderr,_fmt)
#else
#define dbgprt(_fmt...)     /**/
#endif

typedef unsigned char byte;

typedef struct {
    const char *map_file;               // filename
    int map_fd;                         // file descriptor
    size_t map_siz;                     // file size
    byte *map_base;                     // base of map area
    byte *map_ptr;                      // current map pointer
    size_t map_off;                     // current file offset
    struct stat map_st;                 // stat syscall
} filemap;

// mapopen -- open the file
void
mapopen(filemap *map,const char *file,int mode,filemap *mapfm)
{
    int prot;
    int err;

    memset(map,0,sizeof(filemap));

    map->map_file = file;
    map->map_fd = open(map->map_file,mode,0644);
    if (map->map_fd < 0)
        sysfault("mapopen: unable to open '%s' -- %s\n",
            map->map_file,strerror(errno));

    // create new file
    if (mode & O_CREAT) {
        map->map_siz = mapfm->map_siz;
        err = ftruncate(map->map_fd,map->map_siz);
        if (err < 0)
            sysfault("mapopen: unable to ftruncate -- %s\n",strerror(errno));
        map->map_off = 0;
    }

    // process existing file
    else {
        fstat(map->map_fd,&map->map_st);
        map->map_siz = map->map_st.st_size;
        map->map_off = map->map_siz - 1;
        lseek(map->map_fd,map->map_off,0);
    }

    prot = 0;
    if (mode & O_CREAT)
        prot |= PROT_WRITE;
    else
        prot |= PROT_READ;

    do {
        if (map->map_siz == 0)
            break;

        map->map_base = mmap(NULL,map->map_siz,prot,MAP_SHARED,map->map_fd,0);
        if (map->map_base == MAP_FAILED)
            sysfault("mapopen: unable to map map_file='%s' map_siz=%ld -- %s\n",
                map->map_file,map->map_siz,strerror(errno));

        // get starting offset for file
        map->map_ptr = map->map_base + map->map_off;
    } while (0);
}

// mapclose -- close the file
void
mapclose(filemap *map)
{

    if (map->map_base != NULL)
        munmap(map->map_base,map->map_siz);
    map->map_base = NULL;

    if (map->map_fd >= 0)
        close(map->map_fd);
    map->map_fd = -1;
}

// mapreverse -- reverse copy lines in file
void
mapreverse(const char *srcfile,const char *dstfile)
{
    filemap src;
    filemap dst;
    byte *base;
    byte *prev;
    byte *cur;
    ssize_t lhslen;
    ssize_t rawlen;
    ssize_t curlen;

    mapopen(&src,srcfile,O_RDONLY,NULL);
    mapopen(&dst,dstfile,O_RDWR | O_CREAT,&src);

    base = src.map_base;

    // point past last char in file
    lhslen = src.map_siz;
    prev = base + lhslen;

    do {
        // empty file
        if (lhslen <= 0)
            break;

        // assume file with last line that has _no_ newline

        // bug out if short file
        cur = prev - 1;
        if (cur < base) {
            dbgprt("mapreverse: SHORT\n");
            break;
        }

        // well behaved file with newline as last char
        if (*cur == '\n') {
            dbgprt("mapreverse: NICE\n");
            lhslen -= 1;
        }
    } while (0);

    // copy over the bulk of the file
    while (lhslen > 0) {
        dbgprt("mapreverse: LOOP lhslen=%ld prev=%ld\n",lhslen,prev - base);

        // locate next (i.e. "previous") line
        cur = memrchr(base,'\n',lhslen);

        // copy over final part
        if (cur == NULL) {
            dbgprt("mapreverse: FINAL\n");
            break;
        }

        // get length of current line (including newline on left)
        rawlen = prev - cur;
        dbgprt("mapreverse: CURLEN cur=%ld prev=%ld rawlen=%ld\n",
            cur - base,prev - base,rawlen);

        // remove newline on left from copy buffer and length
        curlen = rawlen - 1;

        // copy current line
        dbgprt("mapreverse: COPY\n");
        memcpy(dst.map_ptr,cur + 1,curlen);
        dst.map_ptr += curlen;
        dst.map_siz -= curlen;

        // cut back on the length we scan
        lhslen = cur - base;

        // point one past the newline we just found
        prev = cur + 1;
    }

    // copy over final part
    if (dst.map_siz > 0) {
        dbgprt("mapreverse: FINAL map_siz=%ld\n",dst.map_siz);
        memcpy(dst.map_ptr,base,dst.map_siz);
    }

    mapclose(&src);
    mapclose(&dst);
}

// main -- main program
int
main(int argc,char **argv)
{
    char *cp;

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        switch (cp[1]) {
        default:
            break;
        }
    }

    setlinebuf(stderr);

    if (argc != 2)
        sysfault("main: must have exactly two arguments\n");

    mapreverse(argv[0],argv[1]);

    return 0;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48