You can call clearerr(stdin)
to clear the end-of-file (and error) conditions:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
int c;
/* Consume standard input */
while ((c = getchar()) != EOF) {
putchar(c);
}
/* Clear the error condition */
clearerr(stdin);
printf("Please provide more input.\n");
fflush(stdout);
/* Consume more standard input */
while ((c = getchar()) != EOF) {
putchar(c);
}
return EXIT_SUCCESS;
}
However, this is the wrong approach. If you run echo Hello | ./io2
, the program will not wait for additional input, because standard input is provided by echo, and is no longer connected to the terminal.
The proper approach is to use command-line parameters to specify the file name, and read it using a separate FILE handle:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
FILE *in;
int c;
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *arg0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", arg0);
fprintf(stderr, " %s FILENAME\n", arg0);
fprintf(stderr, "\n");
fprintf(stderr, "This program reads and outputs FILENAME, then\n");
fprintf(stderr, "prompts and reads a line from standard input.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
/* Open specified file. */
in = fopen(argv[1], "r");
if (!in) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
/* Read and output file, character by character ("slow") */
while ((c = getc(in)) != EOF) {
putchar(c);
}
/* Check if the EOF indicated an error. */
if (ferror(in)) {
fprintf(stderr, "%s: Read error.\n", argv[1]);
return EXIT_FAILURE;
}
/* Close the input file. Be nice, and check for errors. */
if (fclose(in)) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
/* Prompt for new input. */
printf("Please input something.\n");
fflush(stdout);
while (1) {
c = getchar();
if (c == EOF || c == '\n' || c == '\r')
break;
putchar(c);
}
printf("All done.\n");
return EXIT_SUCCESS;
}
There are several things worthy of note here:
argv[0]
is the command itself (./io2
in your example). The first command-line argument is argv[1]
. Because argc
is the number of entries in argv
, argc == 1
means there are no arguments; argc == 2
means there is one argument, and so on.
If argc == 2
, then argv[0]
and argv[1]
are valid. In POSIXy systems like Linux and BSDs and Mac, and standard C libraries conforming to C11, argv[argc] == NULL
, and is safe to access.
The if
line relies on C logic rules. In particular, if you have expr1 || expr2
, and expr1
is true, expr2
is never evaluated.
This means that if argc != 2
, the strcmp()
checks are not evaluated at all.
The !strcmp(argv[1], "-h")
is true if and only if argv[1]
matches -h
.
Thus, the if
line reads, "if argc says we don't have exactly two elements in argv array, or we have and argv[1] matches -h, or we have and argv[2] matches --help, then".
It is often possible to execute a program without any arguments in POSIXy systems, for example via execl("./io2", NULL, NULL)
or by some other nonstandard trick. This means that it is technically possible for argc
to be zero. In that case, we don't know how this program was executed.
The value of arg0
is a ternary expression which essentially reads, "if argc says we we should have at least one element in argv array, and argv array exists, and the first element in argv array exists, and the first character in that first element is not end of string mark, then arg0 is the first element in argv array; otherwise, arg0 is (this)."
This is only needed because I like printing the usage when run with -h
or --help
as the first parameter. (Almost all command-line programs in POSIXy systems do this.) The usage shows how to run this program, and for this, I want to use the same command that was used to run this program; hence arg0
.
When getc()
/getchar()
/fgetc()
returns EOF
, it means that there is no more input in the stream. This can happen because of end of stream, or because a read error occurred.
I like to carefully check for errors. Some consider it "useless", but to me, error checking is important. As an user, I want to know – no, I need to know if my storage media is producing errors. Therefore, the ferror(in)
check is important to me. It is true (nonzero) only if there was a read/write error (I/O error) when accessing the stream in
.
Similarly, it is possible for fclose(in)
to report a delayed error. I do not believe it is possible to happen for a read-only stream, but it definitely is possible for streams we write to, because the C standard library buffers stream data, and the final underlying write operation can occur when we are closing the stream handle. Even the man 3 fclose man page explicitly says this is possible.
Some programmers say that it is not useful to check for fclose()
errors, because they are so rare. To me, as an user, it is. I want the programs I use to report the errors they detect, instead of assuming "eh, it's so rare I won't bother checking or reporting those"*.
By default, standard output (stdout
) is line-buffered, so technically the fflush(stdout)
is not needed. (The fact that the previous printf() ends with a newline, \n
, means that that printf() should cause the standard output to be flushed.)
Flushing a stream means ensuring the C library actually writes its internal buffer to the output file or device. Here, we definitely want to be sure the user sees the prompt, before we start waiting for input. Thus, while the fflush(stdout)
is technically not needed, here it also provides us human programmers a reminder that at this point, we do need the stdout stream to be flushed to the actual output device (terminal).
It is often useful to redirect program output to a file, or via a pipe as input to another program. Because of this, I like to use the standard error (stderr
) for error messages and the usage information.
If the user runs the program incorrectly, or an error occurs, with the output redirected to a file or piped to another program, they will typically still see the standard error output. (It is possible to redirect standard error too, though.)