使用fscanf读取字符串和空行

使用fscanf读取字符串和空行,c,scanf,C,Scanf,我有一个包含关键字和整数的文本文件,可以访问文件流来解析这个文件 我可以通过执行以下操作来解析它 while(fscanf(stream,“%s”,word)!=-1)获取文件中的每个单词和int进行解析,但我遇到的问题是,我无法检测到空行“\n”,然后我需要检测它。我可以看到\n是%s无法检测到的字符。如何修改fscanf以获得下线字符?您可以完全按照自己的意愿使用fscanf,但是正确执行所需的检查和验证的数量,与使用正确的面向行的输入函数(如fgets)相比,完全是痛苦的 使用fgets(

我有一个包含关键字和整数的文本文件,可以访问文件流来解析这个文件

我可以通过执行以下操作来解析它
while(fscanf(stream,“%s”,word)!=-1)
获取文件中的每个单词和int进行解析,但我遇到的问题是,我无法检测到空行“\n”,然后我需要检测它。我可以看到\n是%s无法检测到的字符。如何修改fscanf以获得下线字符?

您可以完全按照自己的意愿使用
fscanf
,但是正确执行所需的检查和验证的数量,与使用正确的面向行的输入函数(如
fgets
)相比,完全是痛苦的

使用
fgets
(或POSIX
getline
)检测空行不需要任何特殊的操作,也不需要读取正常行。例如,要使用
fgets
读取一行文本,只需提供足够大小的缓冲区,并进行一次调用,将
'\n'
读取并包括到
buf

while (fgets (buf, BUFSZ, fp)) {        /* read each line in file */
要检查该行是否为空行,只需检查
buf
中的第一个字符是否为
'\n'
字符,例如

    if (*buf == '\n')
        /* handle blank line */
或者,在正常过程中,您将通过获取长度并用nul终止字符覆盖
'\n'
来删除尾随'
\n'
。在这种情况下,您只需检查长度是否为
0
(删除后),例如

注意:如果最后一个字符不是
'\n'
,则您知道该行比缓冲区长,并且该行中的字符保持未读状态,并且将在下次调用
fgets
时读取,或者您到达文件末尾时,非POSIX行结束于最后一行)

总而言之,例如使用
fgets
识别空行,并提供打印完整行(即使该行超过缓冲区长度),您可以执行以下操作:

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

#define BUFSZ 4096

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

    size_t n = 1;
    char buf[BUFSZ] = "";
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    while (fgets (buf, BUFSZ, fp)) {        /* read each line in file */
        size_t len = strlen (buf);          /* get buf length */
        if (len && buf[len-1] == '\n')      /* check last char is '\n' */
            buf[--len] = 0;                 /* overwrite with nul-character */
        else {   /* line too long or non-POSIX file end, handle as required */
            printf ("line[%2zu] : %s\n", n, buf);
            continue;
        }   /* output line (or "empty" if line was empty) */
        printf ("line[%2zu] : %s\n", n++, len ? buf : "empty");
    }
    if (fp != stdin) fclose (fp);           /* close file if not stdin */

    return 0;
}
示例使用/输出

$ ./bin/fgetsblankln ../dat/captnjack2.txt
line[ 1] : This is a tale
line[ 2] : empty
line[ 3] : Of Captain Jack Sparrow
line[ 4] : empty
line[ 5] : A Pirate So Brave
line[ 6] : empty
line[ 7] : On the Seven Seas.
那么为什么每个人都推荐
fgets

嗯,让我们看看用<代码> fSCANF做同样的事情,我会让你做判断。首先,

fscanf
不使用
“%s”
格式说明符(默认情况下)或在使用字符类
“%[^\n]”
时读取或包含尾随的
'\n'
(因为它被明确排除)。因此,您无法使用相同的格式字符串读取(1)行有字符的行和(2)行没有字符的行。您要么读取字符并
fscanf
成功,要么不读取字符并遇到匹配失败

因此,正如注释中提到的,您必须使用
fgetc
(或
getc
)预先检查输入缓冲区中的下一个字符是否为
'\n'
字符,如果不是,则使用
ungetc
)将其放回输入缓冲区

进一步添加到您的
fscanf
任务中,您必须独立地验证每个检查、返回并阅读过程中的每个步骤。这将导致大量的检查来处理所有情况,并提供避免未定义行为所需的所有检查

作为这些检查的一部分,在捕获下一个字符时,您需要将读取的字符数限制为比缓冲区中的字符数少一个,以确定行是否太长而无法容纳。处理(无故障)最后一行有非POSIX行结尾的文件需要额外的检查,这是由
fgets
处理而没有问题的

下面是与上面的
fgets
代码类似的实现。完成并理解为什么每一步都是必要的,以及每一次验证都会阻止什么。您可能可以稍微重新排列,但它已被缩减到接近最小值。浏览之后,应该清楚为什么
fgets
是处理空行检查(以及面向行的输入)的首选方法

#包括
#定义BUFSZ 4096
int main(int argc,字符**argv){
int c=0,r=0;
尺寸n=1;
字符buf[BUFSZ]=“”,nl=0;
文件*fp=argc>1?fopen(argv[1],“r”):stdin;
如果(!fp){/*验证文件是否打开以进行读取*/
fprintf(stderr,“错误:文件打开失败“%s”。\n”,argv[1]);
返回1;
}
对于(;){/*循环,直到EOF*/
如果((c=fgetc(fp))='\n')/*检查下一个字符是否为'\n'*/
*buf=0;/*使buf为空字符串*/
否则{
如果(c==EOF)/*检查是否为EOF*/
打破
如果(ungetc(c,fp)=EOF){/*ungetc/validate*/
fprintf(stderr,“错误:ungetc失败。\n”);
打破
}
/*将行读入buf并将“\n”读入nl,处理失败*/
如果((r=fscanf(fp,%4095[^\n]%c],buf,&nl))!=2){
如果(r==EOF){/*EOF(输入失败)*/
打破
}/*检查下一个字符,如果不是EOF,则检查非POSIX下线*/
如果((c=fgetc(fp))!=EOF){
如果(ungetc(c,fp)=EOF{/*ungetit*/
fprintf(stderr,“错误:ungetc失败。\n”);
打破
}/*再次读取处理非POSIX下线的行*/
如果(fscanf(fp,“%4095[^\n]”,buf)!=1){
fprintf(stderr,“错误:fscanf失败。\n”);
打破
}
}
}/*良好的fscanf,验证nl='\n'或行到长*/
否则如果(nl!='\n'){
fprintf(stderr,“错误:行%zu太长。\n”,n);
打破
$ cat ../dat/captnjack2.txt
This is a tale

Of Captain Jack Sparrow

A Pirate So Brave

On the Seven Seas.
$ ./bin/fgetsblankln ../dat/captnjack2.txt
line[ 1] : This is a tale
line[ 2] : empty
line[ 3] : Of Captain Jack Sparrow
line[ 4] : empty
line[ 5] : A Pirate So Brave
line[ 6] : empty
line[ 7] : On the Seven Seas.
#include <stdio.h>

#define BUFSZ 4096

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

    int c = 0, r = 0;
    size_t n = 1;
    char buf[BUFSZ] = "", nl = 0;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    for (;;) {  /* loop until EOF */
        if ((c = fgetc (fp)) == '\n')   /* check next char is '\n' */
            *buf = 0;                   /* make buf empty-string */
        else {
            if (c == EOF)               /* check if EOF */
                break;
            if (ungetc (c, fp) == EOF) {    /* ungetc/validate */
                fprintf (stderr, "error: ungetc failed.\n");
                break;
            }
            /* read line into buf and '\n' into nl, handle failure */
            if ((r = fscanf (fp, "%4095[^\n]%c", buf, &nl)) != 2) {
                if (r == EOF) {         /* EOF (input failure) */
                    break;
                } /* check next char, if not EOF, non-POSIX eol */
                else if ((c = fgetc (fp)) != EOF) {
                    if (ungetc (c, fp) == EOF) {    /* unget it */
                        fprintf (stderr, "error: ungetc failed.\n");
                        break;
                    } /* read line again handling non-POSIX eol */
                    if (fscanf (fp, "%4095[^\n]", buf) != 1) {
                        fprintf (stderr, "error: fscanf failed.\n");
                        break;
                    }
                }
            } /* good fscanf, validate nl = '\n' or line to long */
            else if (nl != '\n') {
                fprintf (stderr, "error: line %zu too long.\n", n);
                break;
            }
        } /* output line (or "empty" for empty line) */
        printf ("line[%2zu] : %s\n", n++, *buf ? buf : "empty");
    }

    if (fp != stdin) fclose (fp);     /* close file if not stdin */

    return 0;
}