【问题标题】:How to parse a csv file with fscanf? [closed]如何使用 fscanf 解析 csv 文件? [关闭]
【发布时间】:2019-09-02 18:09:27
【问题描述】:

我需要用 fscanf 解析一个 csv 文件。 我的文件是这样的:

nroInscricao,nota,data,cidade,nomeEscola
13893,353.9,26/11/2016,,FRANCISCO RIBEIRO CARRIL
13595,472.2,,Salgueiro,ALFREDO GUEDES
13894,614.4,28/11/2016,Recife,JOAO DE MOURA GUIMARAES
13880,403.2,29/11/2016,Fortaleza,ANTONIO DIAS PASCHOAL PR
13881,373.7,,Sao Jose da Tapera,DONIZETTI TAVARES DE LIM
13882,394.8,01/12/2016,Sao Bernardo do Cam,JUSTINO GOMES DE CASTRO 

我需要阅读每个字段。 结构如下:

typedef struct RegDados{

    int numberIns;
    double grade;
    char data[10]; 
    char city[50];
    char name[50];

} RegDados;

它与文件上出现的顺序相同。

有什么想法吗?

【问题讨论】:

  • @RishikeshRaje — fngets --> fgets?如果你很快。
  • 将每一行读入一个字符串。您可以为此使用fscanffgets。对于每个字符串,您可以使用 strtok 根据 , 标记字符串,
  • @Rishikesh Raje 她的一些数据包含空字段。 strtok 在这种情况下无法正常工作。

标签: c file fwrite


【解决方案1】:

应该有一个更简单的方法来做到这一点。不幸的是,scanfstrtok 都不能很好地处理空字段。我已将每个数组的大小增加 1 以容纳字符串上的空终止符。

如果我做得对,它会处理错误的输入:缺少字段、最后一行缺少换行符或数据过大。我使用了getline,它分配缓冲区来容纳数据,而不是依赖固定大小的缓冲区并希望,但请注意,这个函数并不是最便携的。

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

typedef struct RegDados{

    int numberIns;
    double grade;
    char data[11]; 
    char city[51];
    char name[51];

} RegDados;

#define MIN(x,y) ((x) < (y) ? (x) : (y))

int main (int argc, char **argv) {
    RegDados record;
    char *line = NULL;
    size_t length = 0;
    while (getline (&line, &length, stdin) != EOF) {
        memset (&record, 0, sizeof (record));
        char *value = line;
        for (int field = 0; field < 5; field++) {
            char *endfield = strchr (value, ',');
            if (!endfield) {
                endfield = strchr (value, '\n');
                if (!endfield) {
                    endfield = strchr (value, '\0');
                }
            }
            int field_length = endfield - value;
            switch (field) {
                case 0: record.numberIns = atoi (value); break;
                case 1: record.grade = atof (value); break;
                case 2: strncpy (record.data, value, MIN (10, field_length)); break;
                case 3: strncpy (record.city, value, MIN (50, field_length)); break;
                case 4: strncpy (record.name, value, MIN (50, field_length)); break;
            }
            if (*endfield == '\n' || *endfield == '\0') break;
            value = endfield + 1;
        }
        printf ("Number: %d\n  Grade: %lf\n  date: %s\n  city: %s\n  who: %s\n",
            record.numberIns,
            record.grade,
            record.data,
            record.city,
            record.name);
    }
    free (line);
    return 0;
}

最好将其分解为函数(可能是一个parse_student_record,它接受一个字符串并在成功时返回一个新分配的记录,可能还有其他的),我应该使用字符串字段长度的常量和一个枚举字段顺序而不是硬编码值。如果这是一个真实的项目,我会使用strtolstrtof 而不是atoiatof,并使用这些函数提供的endptr 进行一些验证(预计endptr == endfield 否则无效内容),但希望这能让你开始。

【讨论】:

  • 我喜欢你的回答,但唯一的问题是精简验证和使用 atoiatof - 提供零错误报告(最好使用 strtolstrtod提供完整的错误报告。其他 nit 将使用 magic-numbers 而不是定义所需的常量。仍然值得 UV 的努力和方法。
  • 糟糕 - 需要整理的几个警告:"warning: second argument of ‘main’ should be ‘char **’""unused parameter 'argc'"argv 也是如此)。只需使用int main (void);在这种情况下。您的解析代码运行良好,否则 :)
【解决方案2】:

在处理空字段时解析字段的一种灵活而可靠的方法是使用简单的 startend 指针和strcspn,其中@987654322 的字节@参数为",\n"。这样,您可以在每一行中遍历指针,使用 switch() 语句和指针算术将字段分隔为相应的结构成员。

在一个示例中一步一步地进行,您可以执行类似于以下的操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>

#define EMPTY  -1   /* define value for number if empty-fields */
#define NMEMB   5   /* number of members in struct */
#define DATSZ  12   /* 10 not large enough for data string */
#define CNSZ   50   /* chars in city, name */
#define NLINE 128   /* number of struct (csv lines) */
#define MAXC 1024   /* number of chars in read buffer */

typedef struct RegDados {
    int numberIns;
    double grade;
    char data[DATSZ]; 
    char city[CNSZ];
    char name[CNSZ];
} RegDados;

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

    char buf[MAXC];     /* read buffer */
    size_t ndx = 0;     /* struct index */
    /* initialize array of struct */
    RegDados reg[NLINE] = {{ .numberIns = EMPTY, .grade = EMPTY }};
    /* 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;
    }
    fgets (buf, MAXC, fp);  /* read/discard header line */

    /* read each line up to a max of NLINE lines */
    while (ndx < NLINE && fgets (buf, MAXC, fp)) {
        size_t n = 0;   /* current field number */
        char *p = buf,  /* beginning pointer in field */
            *ep = p + strcspn (p, ",\n");   /* end pointer in field */

        while (*p && *p != '\n') {  /* for each field */
            switch (n) {    /* switch on field number */
                case 0:
                    if (ep > p && isdigit (*p)) {   /* non-empty w/digit */
                        char *endptr;   /* endptr for strtol */
                        long tmp = strtol (p, &endptr, 0);  /* convert */
                        if (p == endptr)    /* validate */
                            fputs ("error: no digits, numberIns.\n", stderr);
                        else if (errno)
                            fputs ("error: over/underflow.\n", stderr);
                        else if (tmp < INT_MIN || INT_MAX < tmp)
                            fputs ("error: exceeds range of int.\n", stderr);
                        else
                            reg[ndx].numberIns = tmp;
                    }
                    break;
                case 1:
                    if (ep > p && isdigit (*p)) {   /* non-empty w/digit */
                        char *endptr;   /* endptr for strtod */
                        double tmp = strtod (p, &endptr);   /* convert */
                        if (p == endptr)    /* validate */
                            fputs ("error: no digits, numberIns.\n", stderr);
                        else if (errno)
                            fputs ("error: over/underflow.\n", stderr);
                        else
                            reg[ndx].grade = tmp;
                    }
                    break;
                case 2:
                    if (ep > p && ep - p < DATSZ) { /* chars, will it fit? */
                        memcpy (reg[ndx].data, p, ep - p);  /* copy chars */
                        reg[ndx].data[ep - p] = 0;  /* nul-terminate */
                    }
                    break;
                case 3:
                    if (ep > p && ep - p < CNSZ) {  /* chars, will it fit? */
                        memcpy (reg[ndx].city, p, ep - p);  /* copy chars */
                        reg[ndx].city[ep - p] = 0;  /* nul-terminate */
                    }
                    break;
                case 4:
                    if (ep > p && ep - p < CNSZ) {  /* chars, will it fit? */
                        memcpy (reg[ndx].name, p, ep - p);  /* copy chars */
                        reg[ndx].name[ep - p] = 0;  /* nul-terminate */
                    }
                    break;
                default:    /* set default to indicate error */
                    fputs ("error: you shouldn't get here.\n", stderr);
                    break;
            }
            p = ++ep;       /* set p to beginning of next field */
            ep = p + strcspn (p, ",\n");    /* find end of field */
            n++;    /* increment field count */
        }
        ndx++;      /* increment index */
    }

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

    for (size_t i = 0; i < ndx; i++)    /* output data */
        printf ("%6d %6.1f  %-12s %-20s %s\n", reg[i].numberIns,
                reg[i].grade, reg[i].data, reg[i].city, reg[i].name);

    return 0;
}

输入文件示例

$ cat dat/csvwempty.csv
nroInscricao,nota,data,cidade,nomeEscola
13893,353.9,26/11/2016,,FRANCISCO RIBEIRO CARRIL
13595,472.2,,Salgueiro,ALFREDO GUEDES
13894,614.4,28/11/2016,Recife,JOAO DE MOURA GUIMARAES
13880,403.2,29/11/2016,Fortaleza,ANTONIO DIAS PASCHOAL PR
13881,373.7,,Sao Jose da Tapera,DONIZETTI TAVARES DE LIM
13882,394.8,01/12/2016,Sao Bernardo do Cam,JUSTINO GOMES DE CASTRO

使用/输出示例

$ ./bin/parsecsv dat/csvwempty.csv
 13893  353.9  26/11/2016                        FRANCISCO RIBEIRO CARRIL
 13595  472.2               Salgueiro            ALFREDO GUEDES
 13894  614.4  28/11/2016   Recife               JOAO DE MOURA GUIMARAES
 13880  403.2  29/11/2016   Fortaleza            ANTONIO DIAS PASCHOAL PR
 13881  373.7               Sao Jose da Tapera   DONIZETTI TAVARES DE LIM
 13882  394.8  01/12/2016   Sao Bernardo do Cam  JUSTINO GOMES DE CASTRO

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

    【解决方案3】:

    strsep 将处理空字段:

    #include <string.h>
    #include <stdio.h>
    
    int main(int argc, char * argv[]){
    
        char line[1024];
        char *tok;
        char *copy;
        while(gets(line)){
                    copy=&line;//strdup(line);
                    int pos=0;
                    while((tok=strsep(&copy,","))!=NULL){
                            printf("Token is: %s. Position is: %d\n",tok,pos);
                            pos++;
                            //Do stuff here based on position and null/string value
                    }
                    free(copy);
        }
        return 0;
    }
    
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多