使用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
(或POSIXgetline
)检测空行不需要任何特殊的操作,也不需要读取正常行。例如,要使用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;
}