Reading and writing to a file at the same time in C
Asked Answered
C

3

7

Supposed to swap every two lines in a file until just one line remains or all lines are exhausted. I don't want to use another file in doing so.

Here's my code:

#include <stdio.h>

int main() {
    FILE *fp = fopen("this.txt", "r+");
    int i = 0;
    char line1[100], line2[100];
    fpos_t pos;
    fgetpos(fp, &pos);

    //to get the total line count
    while (!feof(fp)) {
        fgets(line1, 100, fp);
        i++;
    }

    i /= 2;  //no. of times to run the loop
    rewind(fp);

    while (i-- > 0) {  //trying to use !feof(fp) condition to break the loop results in an infinite loop
        fgets(line1, 100, fp);
        fgets(line2, 100, fp);

        fsetpos(fp, &pos);

        fputs(line2, fp);
        fputs(line1, fp);

        fgetpos(fp, &pos);
    }

    fclose(fp);
    return 0;
}

content in this.txt:

aaa
b
cc
ddd
ee  
ffff
gg
hhhh
i
jj

content after running the program

b
aaa
ddd
cc
ddd
c
c

c


i
jj

I've even tried using fseek in place of fgetpos just to get the same wrong result.

From what I figured, after the second while loop has run two times (i.e the first four lines have been processed), the cursor is rightfully at 17th byte where it is supposed to be (as returned by the call to ftell(fp)) and even the file contents after the 4th line are unchanged and somehow for some reason when fgets is called when the loop is running for the third time, the contents read into arrays line1 and line2 are "c\n" and "ddd\n" respectively.

AGAIN, I don't want to use another file to accomplish this, I just need to figure out what exactly is going wrong behind the screen

Any leads would be appreciated. Thank you.

Cook answered 4/2, 2017 at 22:59 Comment(8)
fputs(line1,fp); --> fputs(line1,fp);fflush(fp);Grandniece
why dont you load the file into ram and then write it back when you switched lines? or if this is not possible, rename the file and read and write at the same timeAlmaalmaata
@kaetzacoatl, By loading it into ram you mean store everything into one big char array right ? Or are you talking of some other possibility ? One big char array is definitely an option, but I just wanted to know what's wrong with my code. And renaming and reading and writing at the same time won't make any difference if I am working with just one file throughout, would it ?Cook
@Grandniece yeah, it works now. But I don't understand why is there this explicit need to flush ? Why doesn't it get flushed automatically and under what circumstance it would ? I just read another so question with an accepted answer saying a newline character at the end flushes things automatically. But they were talking about the stdout stream, is that not applicable to file streams ?Cook
If a buffer is used for output (on system), it depends on the implementation when it is flushed. Even though the program flushes from memory, the system may be buffering it.Grandniece
@Grandniece Correct me if I interpreted it wrong, you mean to say while using stdout flushing depends on the implementation of c being used, right !Cook
In any case, it is a protective code to flush the file before moving the current position.Grandniece
@BLUEPIXY: technically, moving the position would suffice for written contents to get flushed, but the OP is not moving the position, he is merely reading the new position.Megara
M
4

There are multiple problems in your code:

  • You do not check if fopen() succeeds, risking undefined behavior.

  • The loop to determine the total number of lines is incorrect.
    Learn why here: Why is “while ( !feof (file) )” always wrong?

  • You do not actually need to compute the total number of lines.

  • You should call fflush() to write the contents back to the file before changing from writing back to reading.

The C Standard specifies this restriction for files open in update mode:

7.21.5.3 The fopen function

[...] output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end-of-file.

This explains why just reading the file position after writing the lines in reverse order causes problems. Calling fflush() should solve this issue.

Here is a corrected version:

#include <stdio.h>

int main(void) {
    FILE *fp;
    char line1[100], line2[100];
    fpos_t pos;

    fp = fopen("this.txt", "r+");
    if (fp == NULL) {
        fprintf(stderr, "cannot open this.txt\n");
        return 1;
    }

    while (fgetpos(fp, &pos) == 0 &&
           fgets(line1, sizeof line1, fp) != NULL &&
           fgets(line2, sizeof line2, fp) != NULL) {

        fsetpos(fp, &pos);
        fputs(line2, fp);
        fputs(line1, fp);
        fflush(fp);    
    }

    fclose(fp);
    return 0;
}
Megara answered 4/2, 2017 at 23:33 Comment(4)
Thank you for your answer. Right on all accounts. I counted the total lines since I wasn't able to get it done using !feof(fp) but seems like doing it that way isn't acceptable either.Cook
"...reading the file position after writing the lines in reverse order causes problems." but the file position was read currently, wasn't it ? call to ftell(fp) returned an appropriate position of the cursor, exactly the count that is supposed to be after calls to fputs. It was fgets which read wrong stuff. I was thinking problem was with fgets, that it was reading unflushed data. Where did I get it wrong ?Cook
@subzero: just reading the file position does not allow changing from writing to reading, so indeed fgets() did not read the correct contents. fflush() fixes the problem. You could also write fgetpos(fp, &pos); fsetpos(fp, &pos);, but I find it more consistent to have a single fgetpos(fp, &pos) at the top of the loop.Megara
Understood. ThanksCook
G
3

The buffer may not necessarily be flushed when changing the current position of the file. So it must be explicitly flushed.

E.g Use fflush(fp);

Change

fputs(line2,fp);
fputs(line1,fp);

to

fputs(line2,fp);
fputs(line1,fp);
fflush(fp);
Grandniece answered 4/2, 2017 at 23:17 Comment(2)
I'm not that familar with C file handling, but why do you need to flush it? Shouldn't the input buffer stay unchanged, no matter if you flush or not? Since you don't read what you've written, it should have no effect. Please explain.Almaalmaata
What you say seems ambiguous. As a point, The change must be reflected in the file.Grandniece
E
2

Why not use two file pointers, both pointing to the same file, one to read and one to write? No need to keep track of the file position, no need to seek around, no need to flush then.

This approach spares you a lot of complicated stuff. Those unnecessary efforts are better invested in some sophisticated error checking/logging like below ;-):

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

int main(void) 
{
  int result = EXIT_SUCCESS;

  size_t blocks = 0;

  int l1_done = 0;
  int l2_done = 0;

  FILE *fpin = fopen("this.txt", "r");
  FILE *fpout = fopen("this.txt", "r+");

  if (NULL == fpin)
  {
    result = EXIT_FAILURE;
    perror("fopen() to for reading failed");
  }    

  if (NULL == fpout)
  {
    result = EXIT_FAILURE;
    perror("fopen() for writing failed");
  }    

  while (EXIT_SUCCESS == result && !l1_done && !l2_done)
  {
    result = EXIT_FAILURE;

    char line1[100];
    char line2[100];

    if ((l1_done = (NULL == fgets(line1, sizeof line1, fpin))))
    {
      if (ferror(fpin))
      {
        fprintf(stderr, "Reading line %zu failed.\n", 2*blocks);
        break;
      }
    }

    if ((l2_done = (NULL == fgets(line2, sizeof line2, fpin))))
    {
      if (ferror(fpin))
      {
        fprintf(stderr, "Reading line %zu failed.\n", 2*blocks + 1);
        break;
      }
    }

    {
      size_t len = strlen(line1);

      if (((sizeof line1 - 1) == len) && ('\n' != line1[len]))
      {
        fprintf(stderr, "Line %zu too long or new-line missing.\n", 2*blocks);
        break;
      } 
    }

    {
      size_t len = strlen(line2);

      if (((sizeof line2 - 1) == len) && ('\n' != line2[len]))
      {
        fprintf(stderr, "Line %zu too long or new-line missing.\n", 2*blocks + 1);
        break;
      }
    } 

    if (!l2_done)
    {
      if (EOF == fputs(line2, fpout))
      {
        fprintf(stderr, "Writing line %zu as line %zu failed.\n", 2*blocks + 1, 2*blocks);
        break;
      }
    } 

    if (!l1_done)
    {
      if (EOF == fputs(line1, fpout))
      {
        fprintf(stderr, "Writing line %zu as line %zu failed.\n", 2*blocks, 2*blocks + 1);
        break;
      } 
    }

    ++blocks;

    result = EXIT_SUCCESS;
  }

  if (EXIT_SUCCESS == result && !ll_done && l2_done)   
  {
    fprintf(stderr, "Odd number of lines.\n");
  }

  fclose(fpin);  /* Perhaps add error checking here as well ... */
  fclose(fpout);  /* Perhaps add error checking here as well ... */

  return result;
}
Excavation answered 5/2, 2017 at 13:4 Comment(2)
Thank you for your answer. Nice workaround and very graceful error checking. Just one little thing, shouldn't f1_done && f2_done be l1_done && l2_done in the bottom-most if statement.Cook
@subzero: "Just one little thing ...": Definitely. Thanks for pointing me to this sloppy, untested, late change ... - fixed.Excavation

© 2022 - 2024 — McMap. All rights reserved.