-1

I am trying to read input from stdin with fread(). However i am have a problem, the loop will not terminate and instead keeps reading.

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

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "argument err");
        return -1;
    }
   
    FILE *in  = fopen(argv[1], "w");

    if (in == NULL) {
        fprintf(stderr, "failed to open file");
        return -1;
    }
   
    char buffer[20];
  
    size_t ret;

    while ((ret = fread(buffer, 1, 20, stdin)) > 0) {
           if (fwrite(buffer, 1, ret, in) != ret) {
               if (ferror(in) != 0) {
                    perror("write err:");
               } 
           }
    }
    
    return 0;
}

How can i make this loop terminate when EOF is reached? i have tried using ctrl+D but that just seems like a strange way to stop taking input.

I guess what i want is to use fread() to read multiple arbitrary amounts of data in chunks of 20 bytes and then somehow stop.

3 Answers3

2

How can i make this loop terminate when EOF is reached?

When do you think EOF is reached? Really. When you are providing input interactively, how is the system or the program supposed to know that you've entered all the data you want the program to consume?

i have tried using ctrl+D but that just seems like a strange way to stop taking input.

It is exactly the way to signal a soft EOF to a POSIX terminal. Since you want the loop to stop when EOF is encountered, it seems absolutely natural to me to use ctrl+D for the purpose when providing data interactively. That's not the only way you could signal the end of the input, but it has a lot going for it.

I guess what i want is to use fread() to read multiple arbitrary amounts of data in chunks of 20 bytes and then somehow stop.

Again: how is the program supposed to know when it has consumed all the "multiple arbitrary amounts" of data that you decide to provide on a given run? An EOF signal is an eminently reasonable choice for multiple reasons, and the way to deliver that from a POSIX terminal interface is ctrl+D.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

As pointed out before you are reading from an eternal stream, this means that stdin don't naturally have a EOF (or <=0) value.

If you want your loop to terminate, you will have to add a termination condition, like a certain character, word or all type of value. After that you could use a break or a return in some case. You could also search if your terminal emulator support the insertion of an EOF value into the stdin, which is pretty common (But very platform dependent).

ADD: On my system, typical linux, CTRL+D is for an EOF insertion in stdin. It seems that you found this out yourself, and if you want your program to know where to stop you will need to use this.

You cand also send a signal to your program, usually done with a shortcut like CTRL+D, CTRL+C, CTRL+T etc... there is all sort of signal, which can be sent by your system or/and your TE and you just have to implement in your program the corresponding signal receiver.

yaourtiere
  • 36
  • 6
  • 1
    `EOF` is not a byte character, that can be inserted in the input. But a completely different value (not a byte, but an `int` value, different to any of the 256 values that can be in a `char`) IMHO you have a wrong idea of what `EOF` is. it is used as some means to indicate that an end of file condition has been detected (which consists of reading actually zero characters from the `read()` system call) and not something that gets inserted in the data stream. – Luis Colorado Sep 02 '22 at 19:39
  • 1
    BTW, Control-D doesn't send any signal, but is interpreted by the tty driver, which results in giving to the process the already gathered data, by first deleting the Ctrl-D from the data stream. So nothing is inserted, but on the reverse, it is deleted from the input stream. – Luis Colorado Sep 02 '22 at 19:41
  • I didn't mean to say that Ctrl-D send a signa, mybad. For the `EOF` I was aware that it is just a return code placed after the last byte of a file to indicate its end on reading, and not a real value stored in the file or the stream. My answer is not very clear on this, thanks for your correction, I'll do further research to understand best what I was talking about. – yaourtiere Sep 03 '22 at 09:36
0

How can i make this loop terminate when EOF is reached? i have tried using ctrl+D but that just seems like a strange way to stop taking input.

fread and fwrite are there to read data records, so they (both) take the number of records to read and the size of the record. If the available data doesn't fit on a full record, you will not get the full record at all (indeed, the routines return the number of full records read, and the partial read will be waiting for the next fread() call.)

All the calls in stdio.h package are buffered, so the buffer holds the data that has been read (from the system) but not yet consumed by the user, and so, this makes me to wonder why are you trying to use a buffer to read data that is already buffered?

EOF is produced when you are trying to read one record and the fread() call results in a true end of file from the system (this normally requires two calls, the first to complete the remaining data, the second resulting in no data ---zero bytes--- returned from the system) So you have to distinguish two cases:

  • fread() returns 0 in case it has read something, but is not enough to complete a record.
  • fread() returns EOF in case it has read nothing (the true end of file is reached) As I've said above, fread() & fwrite() will read/write full records (this is useful when your data is a struct with a fixed length, but normally not when you can have extra data at the end)

The way to terminate the loop should be something like this:

    while ((ret = fread(buffer, 1, 20, stdin)) >= 0) {
           if (fwrite(buffer, 1, ret, in) != ret) {
               if (ferror(in) != 0) {
                    perror("write err:");
               } 
           }
    }
    /* here you can have upto 19 bytes in the buffer that cannot
     * be read with that record length, but you can read individually
     * with fgetc() calls. */

so, if you read half a record (at end of file) only at the next fread() it will detect the end of file (by reading nothing) and you will be free of ending. (beware that the extra data that doesn't fill a full buffer, still needs to be read by other means)

The cheapest and easiest way to solve this problem (to copy a file from one descriptor to another) is described in K&R (in the first edition) and has not yet have better code to void it, is this:

int c;
while ((c = fgetc(in)) != EOF)
    fputc(c, out);

while it seems to read the characters one by one, it actually makes a call to read(2) to completely fill a full buffer of data, and return just one character, next characters will be taken from the buffer, saving calls to read(), and the same happens to fputc() (it fills the buffer until it's full, then flushes it, in a single call to write()).

Many people has tried to defeat the code above, without any measurable gain in efficience. So, my hint is be simple, that the world is complicated enough to force you to go complex.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31