在程序中更改输入流

Change input stream mid-program

我有以下程序可以打印文件中的文本:

#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()(当不将文件重定向到标准输入时)?

现在看来,如果我在最后继续执行 getchar(),它似乎只会继续打印 EOF 字符。

我之前的建议行不通,因为 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", 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;
}

这里有几点值得注意:

  • argv[0] 是命令本身(./io2 在你的例子中)。第一个命令行 参数 argv[1]。因为argcargv中的条目数,argc == 1表示没有参数argc == 2表示只有一个参数,以此类推。

    如果argc == 2,则argv[0]argv[1]有效。在 POSIXy 系统中,如 Linux 和 BSDs 和 Mac,以及符合 C11 的标准 C 库,argv[argc] == NULL,并且可以安全访问。

  • if行依赖于C逻辑规则。特别是,如果您有 expr1 || expr2,并且 expr1 为真,则永远不会计算 expr2

    这意味着如果 argc != 2,则根本不会评估 strcmp() 检查。

    当且仅当 argv[1] 匹配 -h.

    时,!strcmp(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()returnsEOF时,表示流中没有更多的输入.这可能是由于流结束或发生读取错误而发生的。

    我喜欢仔细检查错误。有些人认为它“无用”,但对我来说,错误检查很重要。作为用户,我想知道 – 不,我 需要 知道我的存储介质是否产生错误。因此,ferror(in) 检查对我来说很重要。仅当访问流 in.

    时出现 read/write 错误(I/O 错误)时才为真(非零)

    同样,fclose(in)也有可能报延迟错误。我不相信只读流有可能发生,但我们写入的流绝对有可能发生,因为 C 标准库缓冲流数据,最终的底层写操作可以在我们关闭流时发生处理。甚至 man 3 fclose 手册页也明确表示这是可能的。

    一些程序员说检查 fclose() 错误没有用,因为它们很少见。对我来说,作为用户,它是。我希望我使用的程序报告它们检测到的错误,而不是假设“呃,我不会费心去检查或报告那些错误”*。

  • 默认情况下,标准输出 (stdout) 是行缓冲的,因此从技术上讲不需要 fflush(stdout)。 (之前的 printf() 以换行符 \n 结尾的事实意味着 printf() 应该导致标准输出被刷新。)

    刷新流意味着确保 C 库实际将其内部缓冲区写入输出文件或设备。在这里,在我们开始等待输入之前,我们肯定希望确保用户看到提示。因此,虽然 fflush(stdout) 在技术上是不需要的,但它也提醒我们人类程序员,此时,我们确实需要将 stdout 流刷新到实际输出设备(终端)。

  • 将程序输出重定向到文件或通过管道作为另一个程序的输入通常很有用。因此,我喜欢使用标准错误 (stderr) 作为错误消息和使用信息。

    如果用户 运行 程序不正确,或者发生错误,输出重定向到文件或通过管道传输到另一个程序,他们通常仍会看到标准错误输出。 (不过,也可以重定向标准错误。)