【问题标题】:C program to read file reading an extra lineC程序读取文件读取额外的行
【发布时间】:2019-11-04 03:04:23
【问题描述】:

我正在处理的代码涉及读取具有以下结构的输入文件:

(spaces)name(spaces) val (whatever) \n
(spaces)name(spaces) val (whatever) \n
(spaces)name(spaces) val (whatever) \n

其中空格表示任意数量的空格。我的代码应该同时给出名称和值。还有另一种情况,在“#”之后的行中的所有内容都被忽略(视为注释)。输出应该是:

"name: (name) value: val \n" 

代码大部分都在工作,除了它添加了一个额外的行,它将创建一个 set name= null 和 val 到最后读取的数字是什么。例如我的测试文件:

a 12
b     33
#c 15
nice 6#9

输出是:

Line after:  a 12

name: a value: 12 :
Line after: b     33 

name: b  value: 33 :
Line after:  # c 15

Line after:  nice 6#9

name: nice value: 6 :
Line after:

name:  value: 6 : //why is this happening

代码在这里。

void readLine(char *filename)
{
    FILE *pf;
    char name[10000]; 
    char value[20];
    pf = fopen(filename, "r");
    char line[10000];
    if (pf){
        while (fgets(line, sizeof(line), pf) != NULL) {
            //printf("Line: %s\n",line);            
                printf("Line after: %s\n",line); 
                while(true){
                    int i=0;
                    char c=line[i]; //parse every char of the line
                            //assert(c==' ');
                            int locationS=0; //index in the name
                            int locationV=0; //index in the value
                            while((c==' ')&& i<sizeof(line)){
                                //look for next sequence of chars
                                ++i;
                                c=line[i];
                                if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c!=' ');
                            while (c!=' '&&i<sizeof(line))
                            {
                                name[locationS]=c;
                                locationS++;
                                //printf("%d",locationS);
                                ++i;
                                c=line[i];if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c==' ');
                            while(c==' '&&i<sizeof(line)){
                                //look for next sequence of chars
                                ++i;
                                c=line[i];
                                if(c=='#'){
                                    break;
                                }
                            }
                            if(c=='#'){ break;}
                            assert(c!=' ');
                            printf("\n");
                             while ((c!=' '&& c!='\n')&&i<sizeof(line))
                            {
                                value[locationV]=c;
                                locationV++;
                                ++i;
                                c=line[i];if(c=='#'){
                                    break;
                                }
                            }
                            printf("name: %s value: %s : \n",name, value);
                            memset(&name[0], 0, sizeof(name));
                            memset(&value[0], 0, sizeof(value));
                            break; //nothing interesting left
                }
        }
        fclose(pf);
    }else{
        printf("Error in file\n");
        exit(EXIT_FAILURE);
    }
}

【问题讨论】:

  • 尝试使用 feof() 而不是 fgets()
  • 其他都一样吗?
  • i&lt;sizeof(line) 应该是 i&lt;strlen(line) 你还有其他逻辑错误。 (从您的输出来看,您的文件末尾似乎有一个额外的'\n'
  • @Dushara - 否。fgets() 的使用是正确的。
  • @DavidC.Rankin 您能否更具体地了解额外的逻辑错误?

标签: c file io


【解决方案1】:

Pasha,你做的一些事情是正确的,但是你却让你想要做的事情变得更加困难。首先,避免在代码中使用 magic-numbers,例如 char name[10000];。而是:

...
#define MAXC 1024   /* if you need a constant, #define one (or more) */

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

    char line[MAXC];
    ...

(你按照规则做得很好不要吝啬缓冲区大小:)

同样,在尝试使用fgets() 读取文件之前,您在打开文件并验证文件是否已打开以供读取方面做得很好。您可以在单个块中进行该验证并在那时处理错误 - 这将具有减少整个代码其余部分的一级缩进的效果,例如

    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

现在打开文件并验证它已打开以供读取并已处理任何错误,您可以继续读取文件中的每一行。除非您将名称存储在需要在读取循环中存活的数组中,否则您可以简单地在读取循环块中声明 name[MAXC];,例如

    while (fgets (line, MAXC, fp)) {    /* read each line of input */
        char name[MAXC];                /* storage for name */
        int val;                        /* integer value for val */

(注意:我们没有声明另一个数组来保存值,而是简单地将val声明为int,并将使用sscanf解析nameval转换值直接到int当时)

任何时候您使用面向行的输入函数(如fgets() 或POSIX getline(),您都需要修剪'\n' 读取并包含在已填充的缓冲区中。您可以使用strcspn 轻松完成此操作,请参阅strspn(3) - Linux manual page。这是一个简单的单一调用,您可以使用strcspn 的返回值作为'\n' 的索引,以便用'\n' 覆盖nul-terminating 字符(即'\0',或简称为0

        line[strcspn (line, "\n")] = 0; /* trim '\n' from end of line */

现在您需要做的就是检查line 中是否存在第一个'#',它标志着评论的开始。如果找到,您只需使用 nul-terminating 字符覆盖 '#',就像对 '\n' 所做的那样,例如

        line[strcspn (line, "#")] = 0;  /* overwrite '#' with nul-char */

现在您已经有了自己的行并删除了'\n' 和任何可能存在的注释,您可以检查line 是否为空(这意味着它以'#' 开头或只是一个空行只包含'\n')

        if (!*line)                     /* if empty-string */
            continue;                   /* get next line */

(注意: if (!*line) 只是 if (line[0] == 0) 的简写。当您取消引用缓冲区时,例如 *line 您只是将第一个元素(第一个字符)返回为 *line == *(line + 0) in指针表示法,相当于数组索引表示法中的 *(line + 0) == line[0][] 也作为解引用操作。)

现在只需使用sscanf 直接从line 解析nameval"%s""%d" 转换说明符都将忽略转换说明符之前的所有前导空格。只要name 本身不包含空格,您就可以使用这个简单的方法。正如您验证文件打开的返回,您将验证sscanf 的返回以确定如果您指定的转化次数成功发生。例如:

        if (sscanf (line, "%1023s %d", name, &val) == 2)  /* have name/value? */
            printf ("\nline: %s\nname: %s\nval : %d\n", line, name, val);
        else
            printf ("\nline: %s (doesn't contain name/value\n", line);

(注意: 通过为您的字符串使用 field-width 修饰符,例如 "%1023s" 您可以保护 name 的数组边界。字段宽度限制sscanf 从写多于 1023 char + \0 到命名。这不能由变量或宏提供,并且是您必须在代码中粘贴 magic-number 的情况之一。 . 对于每条规则,通常都有一两个警告...)

如果您要求进行 2 次转换,并且 sscanf 返回了 2,那么您就知道所请求的两次转换都成功了。此外,由于您为 val 指定了 integer 转换,因此可以保证 value 将包含 integer

仅此而已。剩下的就是关闭文件(如果不是从stdin 读取),你就完成了。一个完整的例子可能是:

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */

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

    char line[MAXC];
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (fgets (line, MAXC, fp)) {    /* read each line of input */
        char name[MAXC];                /* storage for name */
        int val;                        /* integer value for val */

        line[strcspn (line, "\n")] = 0; /* trim '\n' from end of line */
        line[strcspn (line, "#")] = 0;  /* overwrite '#' with nul-char */

        if (!*line)                     /* if empty-string */
            continue;                   /* get next line */

        if (sscanf (line, "%1023s %d", name, &val) == 2)  /* have name/value? */
            printf ("\nline: %s\nname: %s\nval : %d\n", line, name, val);
        else
            printf ("\nline: %s (doesn't contain name/value\n", line);
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
}

(注意:如果您想在修剪'\n' 和cmets 之前打印原始line,只需将line 的打印移到调用strcspn 之前。在@ 上方打印 987654383@,显示调用 line 之前的最终状态 sscanf)

使用/输出示例

使用存储在我系统上dat/nameval.txt 中的输入文件,您可以简单地执行以下操作来读取从stdin 重定向的值:

$ ./bin/parsenameval <dat/nameval.txt

line: a 12
name: a
val : 12

line: b     33
name: b
val : 33

line: nice 6
name: nice
val : 6

(注意: 只需删除重定向 &lt; 即可实际打开并从文件中读取,而不是让 shell 为您执行此操作。六对一,六对一。 )

查看一下,如果您还有其他问题,请告诉我。如果由于某种原因您不能使用任何函数来帮助您解析行并且只能使用指针或数组索引,请告诉我。按照上面的方法,只需一点点努力就可以将每个操作替换为手动操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-10
    • 2011-01-03
    相关资源
    最近更新 更多