CSV 解析函数无法识别空值

标签 c csv sqlite

我有一个包含 7 个字段的 CSV 文件:

me,val1,val2,val3,val4,val5,val6,
me,val1,val2,val3,val4,val5,val6,

我正在使用以下函数对此进行解析:

void readcsv() {
    lk_dispclr();
    lk_disptext(2, 0, "Parsing CSV..", 0);
    lk_disptext(3, 0, "Please Wait..", 0);

    FILE *stream = fopen("input.csv", "r");
    if (stream != NULL) {
        char line[1024];
        while (fgets(line, 1024, stream)) {
            char *tmp = strdup(line);      
            char a1[20] = "";
            char b1[20] = "";
            char c1[20] = "";
            char d1[20] = "";
            char e1[20] = "";
            char f1[20] = ""; 
            char g1[20] = ""; 

            strcat(a1, getcsvfield(tmp, 1));
            strcat(b1, getcsvfield(tmp, 2));
            strcat(c1, getcsvfield(tmp, 3));
            strcat(d1, getcsvfield(tmp, 4));
            strcat(e1, getcsvfield(tmp, 5));
            strcat(f1, getcsvfield(tmp, 6));
            strcat(g1, getcsvfield(tmp, 7));

            //printf("Field 1 would be %s\n", a1);
            //printf("Field 2 would be %s\n", getcsvfield(tmp, 2));
            //printf("Field 2 would be %s\n", getcsvfield(tmp, 3));
            //printf("Field 2 would be %s\n", getcsvfield(tmp, 4));
            //printf("Field 2 would be %s\n", getcsvfield(tmp, 5));
            //printf("Field 2 would be %s\n", getcsvfield(tmp, 6));
            execute("INSERT INTO sdata  (uid,sid,name,area,type,stbamount,pkgamount)"
                    "   VALUES('%s','%s','%s','%s','%s','%s','%s');",
                    a1, b1, c1, d1, e1, f1, g1);
            // NOTE strtok clobbers tmp
            free(tmp);
        }
        lk_dispclr();
        lk_disptext(2, 4, "CSV Imported!", 1);
        lk_getkey();
    } else {
        lk_dispclr();
        lk_disptext(2, 4, "CSV Not Found!", 1);
        lk_getkey();
    }
}

//Used for parsing CSV
const char *getcsvfield(char *line, int num) {
    char buffer[1024] = { 0 };
    strcpy(buffer, line);
    const char *tok;
    for (tok = strtok(buffer, ",");
         tok && *tok;
         tok = strtok(NULL, ",\n"))
    {
        if (!--num)
            return tok;
    }
    return NULL;
}

但是如果第 6 个字段 (val5) 缺失,val6 将被插入表中 val5 的位置,实际上应该是空白的。

我做错了什么?

最佳答案

你的代码有几个问题

  • 主要问题是您在 getcsvfield 中返回一个指向自动存储的指针:您将 line 复制到本地数组 buffer并使用 strtok 来解析它。当您返回第 n 个元素时,tok 指向 buffer 内部,这是一个本地数组。从函数 getcsvfield 返回后引用此数组会调用未定义的行为。您可以通过将字段复制到作为 getcsvfield 的参数接收的缓冲区中来解决此问题。

  • 关于空值,您不能使用strtok 来解析CSV 格式:它首先会跳过所有出现的定界符,因此您不能将空字段作为 序列, 将被解释为单个分隔符。 strtok 是一个使用隐藏全局状态的过时函数,您可能也应该避免在其他地方使用它。

这是一个改进的版本:

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

//Used for parsing CSV
char *getcsvfield(char *dest, int size, const char *line, int num) {
    const char *p;

    for (p = line; *p != '\0' && *p != '\n';) {
        int len = strcspn(p, ",\n");  /* parse field characters */
        if (--num <= 0) {
            if (len >= size)
                len = size - 1;
            memcpy(dest, p, len);
            dest[len] = '\0';
            return dest;
        }
        p += len;
        if (*p == ',')
            p++;
    }
    *dest = '\0';
    return NULL;
}

void readcsv(void) {
    lk_dispclr();
    lk_disptext(2, 0, "Parsing CSV..", 0);
    lk_disptext(3, 0, "Please Wait..", 0);

    FILE *stream = fopen("input.csv", "r");
    if (stream != NULL) {
        char line[1024];
        while (fgets(line, 1024, stream)) {
            char a1[20], b1[20], c1[20], d1[20], e1[20], f1[20], g1[20]; 

            getcsvfield(a1, sizeof a1, line, 1);
            getcsvfield(b1, sizeof b1, line, 2);
            getcsvfield(c1, sizeof c1, line, 3);
            getcsvfield(d1, sizeof d1, line, 4);
            getcsvfield(e1, sizeof e1, line, 5);
            getcsvfield(f1, sizeof f1, line, 6);
            getcsvfield(g1, sizeof g1, line, 7);

            execute("INSERT INTO sdata  (uid,sid,name,area,type,stbamount,pkgamount)"
                    "   VALUES('%s','%s','%s','%s','%s','%s','%s');",
                    a1, b1, c1, d1, e1, f1, g1);
        }
        fclose(stream);
        lk_dispclr();
        lk_disptext(2, 4, "CSV Imported!", 1);
        lk_getkey();
    } else {
        lk_dispclr();
        lk_disptext(2, 4, "CSV Not Found!", 1);
        lk_getkey();
    }
}

请注意,您的插入方法可能允许攻击者通过 CSV 文件执行 SQL 注入(inject)。在上面的示例中,由于每个字段 20 字节的限制,这会很困难,但在其他地方,您在编写 SQL 命令时应该更加小心。 SQlite 也可能对 execute 参数执行完整性检查。

关于CSV 解析函数无法识别空值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35669438/

相关文章:

来自 S3 的 PHP CSV 作为字符串 - 想要转换为数组

Javascript 到 csv 导出编码问题

python - 不能将 sqlite3 参数替换与 PRAGMA 一起使用?

sql-server - 是否有一个 set identity_insert on equivalent for SQLite?

python - 如何在 Mac 上更新 OpenSSL?

c - 如果我有一个 C 函数并且我正在使用 gdb 调试它,我如何找到函数的返回值?

c - 使用 fgets() 的段错误

python - 如何在 python 中一次将巨大的 CSV 文件插入 SQL Server?

java - 如何使用 ContextMenu 删除 Sqlite 中的行?

c - malloc(sizeof(struct Node)) 与 malloc(sizeof(nodeptr)) 的不同行为