问题描述
#include<stdio.h>
int main(void) {
int ch;
while ((ch=getchar()) != EOF) {
putchar(ch);
}
printf("Enter a character Now...\n");
// possible to prompt input Now from a user?
ch = getchar();
printf("The character you entered was: %c\n",(char) ch);
}
并运行它:
$ ./io2 < file.txt
This is a text file
Do you like it?
Enter a character Now...
The character you entered was: �
在此之后,我将如何让用户输入字符。例如,如果我要执行 getchar()
(不将文件重定向到标准输入时)?
现在看来,如果我在最后继续执行 EOF
,它似乎只会继续打印 getchar()
字符。
解决方法
我之前的建议行不通,因为 cat
命令只会将文件和标准输入合并为一个并将其提供给您的程序,您最终会得出相同的结论。
如果你的程序需要这个文件,它应该直接读取它,然后从标准输入中获取其余的输入...
#include<stdio.h>
#include<stdlib.h>
int main(void) {
int ch;
FILE* file = fopen("file.txt","r");
if (file == NULL) {
perror("fopen");
return EXIT_FAILURE;
}
while ((ch=fgetc(file)) != EOF) {
putchar(ch);
}
fclose(file);
... // now just read from stdin
return EXIT_SUCCESS;
}
,
您可以调用 clearerr(stdin)
来清除文件结束(和错误)条件:
#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;
}
然而,这是错误的方法。如果你运行 echo Hello | ./io2
,程序将不会等待额外的输入,因为标准输入是由 echo 提供的,并且不再连接到终端。
正确的做法是使用命令行参数指定文件名,并使用单独的FILE句柄读取:
#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","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,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;
}
这里有几点值得注意:
-
argv[0]
是命令本身(在您的示例中为./io2
)。第一个命令行参数是argv[1]
。因为argc
是argv
中的条目数,所以argc == 1
表示没有参数;argc == 2
表示有一个参数,依此类推。如果
argc == 2
,则argv[0]
和argv[1]
有效。在 Linux、BSD 和 Mac 等 POSIXy 系统以及符合 C11 的标准 C 库中,argv[argc] == NULL
可以安全访问。 -
if
行依赖于 C 逻辑规则。特别是,如果您有expr1 || expr2
,并且expr1
为真,则永远不会评估expr2
。这意味着如果
argc != 2
,则根本不评估strcmp()
检查。当且仅当
!strcmp(argv[1],"-h")
匹配argv[1]
时,-h
为真。因此,
if
行读取,“如果 argc 说我们在 argv 数组中没有恰好两个元素,或者我们有和 argv[1] 匹配 -h,或者我们有和 argv [2] 匹配 --help,then". -
在 POSIXy 系统中通常可以执行没有任何参数的程序,例如通过
execl("./io2",NULL,NULL)
或其他一些非标准技巧。这意味着argc
为零在技术上是可能的。在那种情况下,我们不知道这个程序是如何执行的。arg0
的值是一个三元表达式,它本质上是这样的,“如果 argc 说我们应该在 argv 数组中至少有一个元素,并且 argv 数组存在,并且 argv 数组中的第一个元素存在,并且第一个元素中的第一个字符不是字符串结束标记,则 arg0 是 argv 数组中的第一个元素;否则,arg0 是 (this)。"这只是因为我喜欢在以
-h
或--help
作为第一个参数运行时打印用法。 (POSIXy 系统中几乎所有的命令行程序都这样做。)用法说明了如何运行这个程序,为此,我想使用与运行这个程序相同的命令;因此arg0
。 -
当
getc()
/getchar()
/fgetc()
返回EOF
时,表示流中没有更多输入。发生这种情况的原因可能是流结束,或者发生了读取错误。我喜欢仔细检查错误。有些人认为它“没用”,但对我来说,错误检查很重要。作为用户,我想知道 - 不,我需要知道我的存储介质是否产生错误。因此,
ferror(in)
检查对我很重要。仅当访问流in
时发生读/写错误(I/O 错误)时才为真(非零)。同样,
fclose(in)
也有可能报告延迟错误。我不相信只读流有可能发生,但我们写入的流肯定有可能发生,因为 C 标准库缓冲流数据,并且当我们关闭流时可能会发生最终的底层写操作处理。甚至 man 3 fclose 手册页都明确表示这是可能的。有些程序员说检查
fclose()
错误没有用,因为它们太罕见了。对我来说,作为用户,它是。我希望我用来报告它们检测到的错误的程序,而不是假设“嗯,这太罕见了,我不会去检查或报告这些错误”*。 -
默认情况下,标准输出 (
stdout
) 是行缓冲的,因此技术上不需要fflush(stdout)
。 (前面的 printf() 以换行符\n
结尾,这意味着 printf() 应该导致标准输出被刷新。)刷新流意味着确保 C 库实际将其内部缓冲区写入输出文件或设备。在这里,我们肯定希望在开始等待输入之前确保用户看到提示。因此,虽然技术上不需要
fflush(stdout)
,但在这里它也提醒我们人类程序员,此时我们确实需要将 stdout 流刷新到实际的输出设备(终端)。 -
将程序输出重定向到文件或通过管道作为另一个程序的输入通常很有用。因此,我喜欢将标准错误 (
stderr
) 用于错误消息和使用信息。如果用户错误地运行程序,或发生错误,将输出重定向到文件或通过管道传输到另一个程序,他们通常仍会看到标准错误输出。 (不过,也可以重定向标准错误。)