【问题标题】:Can't find EOF in fgetc() buffer using STDIN使用 STDIN 在 fgetc() 缓冲区中找不到 EOF
【发布时间】:2014-05-01 08:29:00
【问题描述】:

编辑
问题的解决方案是了解 Ctrl-D 实际在做什么。
在新的空行上,单个 Ctrl-D 将发出 EOF 信号。
但如果该行中已经有字符,第一个 Ctrl-D 会使该行的内容回显到屏幕上(但不会写入STDOUT)。如果字符已经在缓冲区中,则必须发出第二个 Ctrl-D 来发出信号 EOF,从而将缓冲区写入 STDOUT
这可以通过将输出重定向到文件来证明。
EDIT

我正在使用 fgetc() 从stdin 读取输入。我循环直到收到 EOF。在循环中,我根据在按下 Ctrl-D 之前键入的字符构建一个字符串。但我无法找出退出循环的方法,因为从中读取的缓冲区 ch = fgetc() 不包含 EOF。 (EOF 只触发 fgetc() 返回它的第一个值。)

ungetc() 不允许将 EOF 推入缓冲区,推入任何其他字符都有与真实数据混淆的风险,我被卡住了!我已经阅读了很多答案,但它们没有解决这个问题或不适用于我试图实现的用例。

我希望能够对标准输入缓冲区进行计数、查看等操作。

我真的不想读取一整行(或一次读取 X 个字符),因为我正在处理从 fgetc() 到达(edit)的每个字符。

关于如何克服这一困境的任何建议? (不使用 NCurses)

我正在使用 Ubuntu。 EOF = Ctrl-D 这是我正在使用的一些代码:

这行得通,和乔纳森的简单例子一样,但不是我想要的:

int main(int argc, char **argv) {

    int inputChr;

    do {
        inputChr = fgetc(stdin);
        if (inputChr != EOF) {
            fputc( inputChr, stdout);
        }
        if (feof(stdin)) {
            if (ferror(stdin)) {
                perror(NULL);
                return errno;
            }
        }
    } while (inputChr != EOF);
    return EXIT_SUCCESS;
}

但是,这被卡住了,但正在尝试做我想做的事情(edit),但需要再次使用 Ctrl-D:

char *buildLine (FILE *inputSource, char *currLine, int showTabs, int showNonPrint, int *haveLF) {

    int inputChr;
    char *thisLine = malloc(1);
    int inputSize;

    *haveLF = FALSE;
    while ( (inputChr = fgetc(inputSource)) != EOF ) {

        if (ferror(inputSource)) {
            perror(NULL);
        } else {
            if (inputChr == LF) {
                *haveLF = TRUE;
            } else {
                thisLine = strconcat(thisLine,(char *)&inputChr);
            }
        }
    }

    return thisLine;
}

还有一些被问到的代码:

char * strconcat ( char *str1, char * str2) {

    char *newStr = malloc(strlen(str1)+strlen(str2)+1);
    if (newStr == NULL) {
        return NULL;
    }
    strcpy(newStr,str1);
    strcat(newStr,str2);

    return newStr;
}

以下版本会逐个字符处理输入的字符,其工作方式与cat 类似。但我决定先将每个字符处理成一行,然后再应用一些我需要实现的额外转换。这简化了状态机设计,但尝试构建线条可能不是一个好的选择(不使用 NCurses)。 :(

int echoInput( FILE *inputSource, FILE *outputDestination, int numbers, int showEnds) {

    int haveNewLine = TRUE;
    int lineNo = 1;
    int inputChr;

    do {
        inputChr = fgetc(inputSource);
        if (inputChr != EOF) {
            if (numbers && haveNewLine) {
                long lineNoSize = (long) log10(lineNo)+1;   // effectively floor(log10(lineNo)+1) = number digits
                char *lineNoStr =  (lineNoSize<6)?malloc(8):malloc(lineNoSize+2);   // If less than 6 digits, allow for minimum 6 plus tab.  Also +1 for terminator.
                if (lineNoStr == NULL) {
                    printf ("Error::Out of Memory");
                    return ENOMEM;
                }
            sprintf(lineNoStr,"%6d\t",lineNo);  // format lineNo string
                fputs(lineNoStr, outputDestination);    // send string to output
                lineNo++;
                haveNewLine = FALSE;
            }
            if (inputChr == LF) {
                if (showEnds) {
                    fputc('$', outputDestination);  // send char to output
                }
                haveNewLine = TRUE;
            }
            fputc( inputChr, outputDestination);
        }
        if (feof(inputSource)) {
            if (ferror(inputSource)) {
                perror(NULL);
                return errno;
            }
        }
        if (ferror(outputDestination)) {
            perror(NULL);
            return errno;
        }
    } while (inputChr != EOF);
    return EXIT_SUCCESS;
}

【问题讨论】:

  • 您使用的是哪个操作系统?
  • 如果您打算检查EOF,请确保chint
  • 通常在 Linux 中,键入一些字符后按 Ctrl-D 会导致输入流被刷新(因此您的程序可以开始读取它)但不会结束输入。再次按下它,或者在换行符之后直接按下它,会导致流结束。
  • @MattMcNabb: setvbuf 是标准 C 的一部分,如果 stdin 是例如 FIFO,它将完美地工作。至少对于 Unix 系统,如果 stdin 是一个 tty 则它将不起作用,但 C 标准既不要求也不禁止这样做;不是因为标准输入是行缓冲的,而是因为默认情况下,tty 设备本身在输入 ENTER 或某些其他特殊字符之前不会向用户区返回任何内容。
  • 好的;您需要在调用代码中的malloc() 之后添加*thisLine = '\0';;否则,您阅读的内容越界。你也像筛子一样漏水;你需要在strconcat()free(str1);。此外,如果内存分配失败,您的代码会因为读取 NULL 指针而崩溃和烧毁。您应该考虑每次都使用realloc() 而不是malloc();随着时间的推移,它将更具成本效益。

标签: c eof fgetc


【解决方案1】:

这个问题肯定有其他变体,答案足够好,但这里还有一个。

fgetc()(以及getc()getchar())返回的值是int,而不是char。它必须是int,因为可以返回的值集包括char 的每个可能值和一个额外的值EOF,它是负数(而字符都是正数)。尽管 EOF 最常见的是 -1,但您永远不应该按照这种假设进行编码。

两件事可能会出错:

char c;

while ((c = fgetc(stdin)) != EOF)

如果 char 类型是有符号的,那么在到达 EOF 之前,某些字符(通常是 0xFF、通常是 ÿ、y-变音符号、Unicode U+00FF、带有分音符号的拉丁小写字母 Y)将被误解为指示 EOF。

如果类型 char 是无符号的,那么您将永远不会检测到 EOF,因为分配给 c 的值将是 0xFF(正),并且永远不会与 EOF(负值)相等。

您是正确的,您不能使用 ungetc() 将 EOF 推回输入流。

请注意,Control-D(或 Windows 上的 Control-Z)不会将字符添加到输入队列中。相反,它表示没有更多可用的字符(稍微简化了一些事情),这意味着read() 系统调用返回读取的 0 字节,这意味着 EOF。

使用getchar()putchar() 将标准输入复制到标准输出的简单程序是:

int c;
while ((c = getchar()) != EOF)
    putchar(c);

如果您想打开文件并阅读这些文件,您可以调整它以使用fgetc()getc()fputc()putc()。关键是使用int 来保存读取的值。

【讨论】:

  • 我可能已经阅读了其中的大部分内容,已经找了两天了。但他们没有解决我要问的问题。他们通常误解了 fgetc() 返回一个 int 的事实。
  • 如果标准输入是一个终端,你不能在它上面寻找。 Control-D 仅在终端上是特殊的。您可以使用getc() 或相对读取字符;使用ungetc() 可以保证您得到一个字节的回送——有些系统只提供一个字节,而其他系统则提供您需要的尽可能多的回送。如果您想先看一个角色,请这样做;如果前瞻检测到EOF,请不要使用ungetc()。在报告 EOF 后重复调用 getc() 将继续报告 EOF,除非您清除错误或执行查找操作。因此,我不清楚您的问题是什么。
  • 关于ungetc(EOF):“如果c的值等于宏EOF的值,则操作失败,输入流不变。”不是代码不能执行ungetc(EOF, stream),是没有效果。
  • @chux:您引用了标准,或者您引用的手册页引用了标准——这意味着您是正确的。但是,操作失败了,所以如果您正在测试 ungetc() 是否有效,您不应该推回 EOF,因为这保证不会有效。
  • 我强调的想法(来自 C11 规范)是 ungetc(EOF, stream) 不是未定义的行为。可以执行,但不会执行任何操作。正如您所指出的,ungetc(EOF, stream) 不会工作,因为流不会在下一个fgetc() 返回 EOF - 除非流正在或即将进入 EOF 条件。
【解决方案2】:

EOF 通常是一个整数(不是char),它的值与任何有效字符都不相同。

正常的 C 风格是用\0 来终止你正在构建的字符串。当然,理论上可以读取NUL 字符,如果您想处理这些可能性,您需要记录读取的字符数以及读取它们的缓冲区。

【讨论】:

  • 是的,当我提到使用 ungetc() 时一直在考虑这一点,但数据中可能包含 00。我正在为一项任务创建一个类似“猫”的程序。
  • @Nap:在这种情况下,您只需要保持计数即可。 (并注意其他人给出的警告:fgetc 返回 int,而不是 char。)
  • 对此有什么想法吗?数数是容易的部分。我拿什么来比较?
  • @Nap:您与 EOF 进行比较。您的问题在于终端 i/o 的工作方式;如果要在行首以外的地方发送 eof,则需要输入 ctl-d 两次。
  • 很公平,但是当您使用cat 并从stdin 读取时,如果您说键入abc 然后按Ctrl-D,它会立即在同一行上回显abc。如果再次按 Ctrl-D,它将退出。使用上面的代码,我必须按两次 Ctrl-D 才能回显。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-28
  • 2014-01-15
  • 1970-01-01
  • 2018-07-04
  • 1970-01-01
  • 1970-01-01
  • 2012-05-10
相关资源
最近更新 更多